def applySettings(self): """Apply editor and lexer settings """ if self.qscilexer is None: return # Apply fonts and colors config = core.config()["Editor"] defaultFont = QFont(config["DefaultFont"], config["DefaultFontSize"]) self.qscilexer.setDefaultFont(defaultFont) for i in range(128): if self.qscilexer.description(i): font = self.qscilexer.font(i) font.setFamily(defaultFont.family()) font.setPointSize(defaultFont.pointSize()) self.qscilexer.setFont(font, i) # lexer->setColor(lexer->defaultColor(i), i); # TODO configure lexer colors # lexer->setEolFill(lexer->defaultEolFill(i), i); # lexer->setPaper(lexer->defaultPaper(i), i); if config["DefaultDocumentColours"]: self.qscilexer.setPaper(QColor(config["DefaultDocumentPaper"]), i) self.qscilexer.setEolFill(True, i) else: self.qscilexer.setPaper(self.qscilexer.defaultPaper(i), i) self.qscilexer.setEolFill(self.qscilexer.defaultEolFill(i), i) if config["DefaultDocumentColours"]: self.qscilexer.setColor(QColor(config["DefaultDocumentPen"]), 0) else: self.qscilexer.setColor(self.qscilexer.defaultColor(0), 0)
def zoomTo( self, zoomFactor ): " Scales the font in accordance to the given zoom factor " font = QFont( GlobalData().skin.nolexerFont ) newPointSize = font.pointSize() + zoomFactor font.setPointSize( newPointSize ) self.__text.setFont( font ) return
def minimumSizeHint(self): font = QFont(self.font()) font.setPointSize(font.pointSize() - 1) fm = QFontMetricsF(font) return QSize(fm.width(FractionSlider.WSTRING) * self.__denominator, (fm.height() * 4) + FractionSlider.YMARGIN)
def zoomTo(self, zoomFactor): " Scales the font in accordance to the given zoom factor " font = QFont(GlobalData().skin.nolexerFont) newPointSize = font.pointSize() + zoomFactor font.setPointSize(newPointSize) self.__text.setFont(font) return
def detectRevisionMarginWidth( self ): """ Caculates the margin width depending on the margin font and the current zoom """ skin = GlobalData().skin font = QFont( skin.lineNumFont ) font.setPointSize( font.pointSize() + self.getZoom() ) fontMetrics = QFontMetrics( font, self ) return fontMetrics.width( 'W' * self.__maxLength ) + 3
def detectRevisionMarginWidth(self): """ Caculates the margin width depending on the margin font and the current zoom """ skin = GlobalData().skin font = QFont(skin.lineNumFont) font.setPointSize(font.pointSize() + self.getZoom()) fontMetrics = QFontMetrics(font, self) return fontMetrics.width('W' * self.__maxLength) + 3
def minimumSizeHint(self): "Pour être sûr que le texte du chrono ne soit pas tronqué" font = QFont(self.font()) font.setPointSize(font.pointSize() - 1) fm = QFontMetricsF(font) l = fm.width(TracerChrono.TAILLE*2) # + 10. L = fm.height() + 2. return QSize(l, L)
def zoomTo( self, zoomFactor ): """ Scales the font in accordance to the given zoom factor. It is mostly used in diff viewers """ font = QFont( GlobalData().skin.nolexerFont ) origPointSize = font.pointSize() newPointSize = origPointSize + zoomFactor self.setTextSizeMultiplier( float( newPointSize ) / float( origPointSize ) ) return
def setFont(o, v): v = QFont(v) try: name = v.toString().split(',')[0] except (IndexError, ): name = v.rawName() size = v.pointSize() bold = 'Bold' if v.bold() else '' o.setFont(v) o.setText('%s %s %s' % (name, size, bold))
def paintEvent(self, event=None): font = QFont(self.font()) font.setPointSize(font.pointSize() - 1) fm = QFontMetricsF(font) fracWidth = fm.width(FractionSlider.WSTRING) indent = fm.boundingRect("9").width() / 2.0 if not X11: fracWidth *= 1.5 span = self.width() - (FractionSlider.XMARGIN * 2) value = self.__numerator / float(self.__denominator) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.TextAntialiasing) painter.setPen(self.palette().color(QPalette.Mid)) painter.setBrush(self.palette().brush( QPalette.AlternateBase)) painter.drawRect(self.rect()) segColor = QColor(Qt.green).dark(120) segLineColor = segColor.dark() painter.setPen(segLineColor) painter.setBrush(segColor) painter.drawRect(FractionSlider.XMARGIN, FractionSlider.YMARGIN, span, fm.height()) textColor = self.palette().color(QPalette.Text) segWidth = span / self.__denominator segHeight = fm.height() * 2 nRect = fm.boundingRect(FractionSlider.WSTRING) x = FractionSlider.XMARGIN yOffset = segHeight + fm.height() for i in range(self.__denominator + 1): painter.setPen(segLineColor) painter.drawLine(x, FractionSlider.YMARGIN, x, segHeight) painter.setPen(textColor) y = segHeight rect = QRectF(nRect) rect.moveCenter(QPointF(x, y + fm.height() / 2.0)) painter.drawText(rect, Qt.AlignCenter, QString.number(i)) y = yOffset rect.moveCenter(QPointF(x, y + fm.height() / 2.0)) painter.drawText(rect, Qt.AlignCenter, QString.number(self.__denominator)) painter.drawLine(QPointF(rect.left() + indent, y), QPointF(rect.right() - indent, y)) x += segWidth span = int(span) y = FractionSlider.YMARGIN - 0.5 triangle = [QPointF(value * span, y), QPointF((value * span) + (2 * FractionSlider.XMARGIN), y), QPointF((value * span) + FractionSlider.XMARGIN, fm.height())] painter.setPen(Qt.yellow) painter.setBrush(Qt.darkYellow) painter.drawPolygon(QPolygonF(triangle))
def paintEvent(self, event=None): font = QFont(self.font()) font.setPointSize(font.pointSize() - 1) fm = QFontMetricsF(font) fracWidth = fm.width(FractionSlider.WSTRING) indent = fm.boundingRect("9").width() / 2.0 if not X11: fracWidth *= 1.5 span = self.width() - (FractionSlider.XMARGIN * 2) value = self.__numerator / float(self.__denominator) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setRenderHint(QPainter.TextAntialiasing) painter.setPen(self.palette().color(QPalette.Mid)) painter.setBrush(self.palette().brush( QPalette.AlternateBase)) painter.drawRect(self.rect()) segColor = QColor(Qt.green).dark(120) segLineColor = segColor.dark() painter.setPen(segLineColor) painter.setBrush(segColor) painter.drawRect(FractionSlider.XMARGIN, FractionSlider.YMARGIN, span, fm.height()) textColor = self.palette().color(QPalette.Text) segWidth = span / self.__denominator segHeight = fm.height() * 2 nRect = fm.boundingRect(FractionSlider.WSTRING) x = FractionSlider.XMARGIN yOffset = segHeight + fm.height() for i in range(self.__denominator + 1): painter.setPen(segLineColor) painter.drawLine(x, FractionSlider.YMARGIN, x, segHeight) painter.setPen(textColor) y = segHeight rect = QRectF(nRect) rect.moveCenter(QPointF(x, y + fm.height() / 2.0)) painter.drawText(rect, Qt.AlignCenter, QString.number(i)) y = yOffset rect.moveCenter(QPointF(x, y + fm.height() / 2.0)) painter.drawText(rect, Qt.AlignCenter, QString.number(self.__denominator)) painter.drawLine(QPointF(rect.left() + indent, y), QPointF(rect.right() - indent, y)) x += segWidth span = int(span) y = FractionSlider.YMARGIN - 0.5 triangle = [QPointF(value * span, y), QPointF((value * span) + (2 * FractionSlider.XMARGIN), y), QPointF((value * span) + FractionSlider.XMARGIN, fm.height())] painter.setPen(Qt.yellow) painter.setBrush(Qt.darkYellow) painter.drawPolygon(QPolygonF(triangle))
def set_font(self, font): """Set shell font""" if self.lexer() is None: self.setFont(font) else: lexer = self.lexer() for style in range(16): font_i = QFont(font) if font.weight() == QFont.Normal: font_i.setWeight(lexer.defaultFont(style).weight()) lexer.setFont(font_i, style) self.setLexer(self.lexer()) margin_font = QFont(font) margin_font.setPointSize(margin_font.pointSize()-1) self.setMarginsFont(margin_font)
def loadHistoryData(self): tabledata = self.history.toList() self.tablemodel = BeeralyzerTableModel(tabledata) self.historyTableView.setModel(self.tablemodel) self.historyTableView.setShowGrid(True) font = QFont(self.historTableFontFamily, self.historTableFontSize) self.displayHistoryTableFont(font.family(), font.pointSize()) self.historyTableView.setFont(font) vh = self.historyTableView.verticalHeader() vh.setVisible(True) hh = self.historyTableView.horizontalHeader() hh.setStretchLastSection(True) self.historyTableView.setSortingEnabled(True) self.historyTableView.resizeColumnsToContents()
def test_1(self): font = QFont("Arial", 88) def continueFunc(dialog): page = dialog._pageForItem["Editor/Font"] page.lFont.setFont(font) QTest.keyClick(dialog, Qt.Key_Enter) self.openSettings(continueFunc) self.assertEqual(core.config()['Qutepart']['Font']['Family'], font.family()) self.assertEqual(core.config()['Qutepart']['Font']['Size'], font.pointSize()) self.assertEqual(core.workspace().currentDocument().qutepart.font(), font)
def setTimestampMarginWidth(self): " Sets the timestamp margin width " settings = Settings() if settings.ioconsoleshowmargin: skin = GlobalData().skin font = QFont(skin.ioconsolemarginFont) font.setPointSize(font.pointSize() + settings.zoom) # The second parameter of the QFontMetrics is essential! # If it is not there then the width is not calculated properly. fontMetrics = QFontMetrics(font, self) # W is for extra space at the right of the timestamp width = fontMetrics.width('88:88:88.888W') self.setMarginWidth(self.TIMESTAMP_MARGIN, width) else: self.setMarginWidth(self.TIMESTAMP_MARGIN, 0) return
def setTimestampMarginWidth( self ): " Sets the timestamp margin width " settings = Settings() if settings.ioconsoleshowmargin: skin = GlobalData().skin font = QFont( skin.ioconsolemarginFont ) font.setPointSize( font.pointSize() + settings.zoom ) # The second parameter of the QFontMetrics is essential! # If it is not there then the width is not calculated properly. fontMetrics = QFontMetrics( font, self ) # W is for extra space at the right of the timestamp width = fontMetrics.width( '88:88:88.888W' ) self.setMarginWidth( self.TIMESTAMP_MARGIN, width ) else: self.setMarginWidth( self.TIMESTAMP_MARGIN, 0 ) return
class ItemPrintDatasource: # If we want to print tables and trees with a good code re-use, we've got to abstract away the # fact that trees have nodes and stuff and start treating it as rows and columns, with some # cells having more indentation than others. That's what this class is about.: def __init__(self, printViewModel, baseFont): self.printViewModel = printViewModel # From core.gui.transaction_print self._rowFont = QFont(baseFont) self._splitFont = QFont(baseFont) self._splitFont.setPointSize(self._splitFont.pointSize()-2) self._headerFont = QFont(self._rowFont) self._headerFont.setBold(True) def columnCount(self): """Returns the number of columns *to print*.""" raise NotImplementedError() def rowCount(self): """Returns the number of rows *to print*.""" raise NotImplementedError() def splitCount(self, rowIndex): raise NotImplementedError() def splitValues(self, rowIndex, splitIndex): raise NotImplementedError() def data(self, rowIndex, colIndex, role): """Returns model data for the index at the *printable* (rowIndex, colIndex) cell.""" raise NotImplementedError() def columnAtIndex(self, colIndex): """Returns the Column instance at index `colIndex`""" raise NotImplementedError() def indentation(self, rowIndex, colIndex): """Returns the indentation pixels for the (rowIndex, colIndex).""" return 0 def rowFont(self): return self._rowFont def splitFont(self): return self._splitFont def headerFont(self): return self._headerFont
def paintEvent(self, event): bgcol = QColor('#111') if self.panic: fgcol = QColor('#e11') else: fgcol = QColor('#ddd') # Size and shit w, h = self.width(), self.height() minsize = min(w,h)*self.scale arcwidth = minsize*0.1 minsize *= 0.86 marginx = (w-minsize)/2 marginy = (h-minsize)/2 # Start drawing shit painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing) painter.setPen(QPen(fgcol, arcwidth, cap=Qt.FlatCap)) #font = QFont('sv basic manual') font = QFont('bank gothic') font.setPointSize(get_font_size(font, minsize-2*arcwidth)) smallfont = QFont(font) smallfont.setPointSizeF(font.pointSize()/2) painter.fillRect(QRectF(0,0,w,h), bgcol) # Timer dial thingy painter.setOpacity(0.05) painter.drawArc(marginx,marginy, minsize,minsize, 0, 5760) painter.setOpacity(1) arclength = min(1, self.totalseconds/self.panictime) * 5760 painter.drawArc(marginx,marginy, minsize,minsize, 90*16, -arclength) # Timer text painter.setFont(font) textoptions = QtGui.QTextOption(Qt.AlignCenter) painter.drawText(QRectF(marginx, marginy, minsize, minsize), self.get_text(0), textoptions) painter.setFont(smallfont) painter.setOpacity(0.5) painter.drawText(QRectF(marginx, marginy+minsize*0.4, minsize, minsize/2), self.get_text(1), textoptions) #painter.setOpacity(0.15) #painter.drawText(QRectF(marginx, marginy+minsize*0.05, minsize, minsize/2), # self.get_text(2), textoptions) painter.end()
def setLanguage( self, language ): self._language = language # grab the language from the lang module if it is a string if ( language and type(language) != XLanguage ): language = XLanguage.byName(language) # collect the language's lexer if ( language ): lexer = language.createLexer(self, self._colorSet) else: lexer = None if ( lexer ): self.setLexer(lexer) lexer.setFont(self.font()) mfont = QFont(self.font()) mfont.setPointSize(mfont.pointSize() - 2) self.setMarginsFont(mfont) else: self.setLexer(None) self.setColorSet(self._colorSet)
class ConfigDialog(QDialog): def __init__(self, parent=None): QDialog.__init__(self, parent) self._layout = QVBoxLayout(self) self._tabdialog = QTabWidget(self) self._layout.addWidget(self._tabdialog) w = _config_dialog.Ui_config_dialog() w.setupUi(self._tabdialog) self.widgets = w self._buttonbox = QDialogButtonBox(QDialogButtonBox.Save|QDialogButtonBox.Cancel) self._buttonbox.setParent(self) signal_connect(self._buttonbox, SIGNAL("accepted()"), self.accept) signal_connect(self._buttonbox, SIGNAL("rejected()"), self.reject) self._layout.addWidget(self._buttonbox) self._layout.setContentsMargins(3,3,3,3) self.font = QFont() self.color = QColor() self.config = yobot_interfaces.component_registry.get_component("client-config") self.account_items = {} signal_connect(w.account_add, SIGNAL("clicked()"), lambda: self.add_modify_account(add=True)) signal_connect(w.account_edit, SIGNAL("clicked()"), lambda: self.add_modify_account(add=False)) signal_connect(w.account_del, SIGNAL("clicked()"), self.remove_account) signal_connect(w.select_color, SIGNAL("clicked()"), lambda: self.change_formatting(color=True)) signal_connect(w.select_font, SIGNAL("clicked()"), lambda: self.change_formatting(font=True)) signal_connect(w.agent_address, SIGNAL("editingFinished()"), self.change_agent) self.connect_global_bool(w.html_relsize, "appearance", "use_html_relsize") self.connect_global_bool(w.show_joinpart, "appearance", "show_joinpart") self.input_validated = True self.setWindowTitle("Yobot Configuration") def connect_global_bool(self, widget, dictname, optname, default=False): signal_connect(widget, SIGNAL("toggled(bool)"), lambda b: self.config.globals.setdefault(dictname, {}).__setitem__(optname, b)) def load_settings(self): w = self.widgets if not self.config: log_warn("config object not available! bailing") return #for font.. appearance = self.config.globals.setdefault("appearance", {}) family = appearance.get("font_family", None) size = appearance.get("font_size", None) color = appearance.get("font_color", None) if family: self.font.setFamily(family) if size: self.font.setPointSize(size) if color: self.color.setNamedColor(color) bold = appearance.get("font_bold", False) italic = appearance.get("font_italic", False) underline = appearance.get("font_underline", False) html_relsize = appearance.get("use_html_relsize", False) show_joinpart = appearance.get("show_joinpart", False) self.font.setBold(bold) self.font.setItalic(italic) self.font.setUnderline(underline) w.html_relsize.setChecked(html_relsize) w.show_joinpart.setChecked(show_joinpart) self.change_formatting() #for the agent... agent = self.config.globals.get("agent_address", None) if agent: w.agent_address.setText(agent) self.change_agent() #for accounts: for a in self.config.accounts: log_warn("got account", a) if a.get("name", None) and a.get("improto", None): #get name and icon name, icon = getProtoIconAndName(getattr(yobotproto, a["improto"], "")) log_debug(icon, name) i = QTreeWidgetItem((a["name"], name)) i.setIcon(1, icon) i.setData(0, ITEM_PLACEHOLDER_ROLE, a) self.account_items[i] = a self.widgets.accounts.addTopLevelItem(i) def remove_account(self): #get current item: w = self.widgets item = w.accounts.currentItem() #get the index (ugh.. this is tedious) itemindex = w.accounts.indexOfTopLevelItem(item) if itemindex == -1: log_err("couldn't get index!") return account = self.account_items[item] #remove the item from the widget: w.accounts.takeTopLevelItem(itemindex) #find the account in our global config list index = -1 for i in xrange(0, len(self.config.accounts)): a = self.config.accounts[i] if str(a["name"]) == str(account["name"]) and str(a["improto"]) == str(account["improto"]): index = i break else: pass if index >= 0: log_debug("index:", index) self.config.accounts.pop(index) #finally, remove it from the mapping self.account_items.pop(item) def add_modify_account(self, add=False): dlg = AccountSettingsDialog(self) if not add: item = self.widgets.accounts.currentItem() if not item: return account = self.account_items.get(item) if not account: return #account = item.data(0, ITEM_PLACEHOLDER_ROLE).toPyObject() dlg.fill_from(account) else: item = QTreeWidgetItem() result = dlg.exec_() if not result == QDialog.Accepted: return new_a = dlg.values item.setText(0, new_a["name"]) #get icon and name... name, icon = getProtoIconAndName(getattr(yobotproto, new_a["improto"], -1)) item.setText(1, name) item.setIcon(1, icon) if add: if self.account_exists(new_a): print "account already exists.. not adding" return item.setData(0, ITEM_PLACEHOLDER_ROLE, new_a) self.widgets.accounts.addTopLevelItem(item) self.config.accounts.append(new_a) else: account.update(new_a) def account_exists(self, d): for a in self.config.accounts: if d["name"] == a["name"] and d["improto"] == a["improto"]: return True return False def change_formatting(self, color=False, font=False): if color: _color = QColorDialog.getColor(self.color, self, "Select Color") if _color.isValid(): self.color = _color elif font: self.font, _ = QFontDialog.getFont(self.font, self, "Select Font") widgetformatter(self.widgets.sample_text, self.font, self.color, klass="QPlainTextEdit") #now, change the config objects.. fmt = self.config.globals["appearance"] fmt.update({ "font_family":str(self.font.family()), "font_size":int(self.font.pointSize()), "font_color":str(self.color.name()), "font_bold":bool(self.font.bold()), "font_italic":bool(self.font.italic()), "font_underline":bool(self.font.underline()) }) def change_agent(self): #bah.. the same boring thing as always s = str(self.widgets.agent_address.text()) if len(s.rsplit(":", 1)) == 2 and not str.isdigit(s.rsplit(":",1)[1]): self.input_validated = False self.widgets.agent_address.setStyleSheet("background-color:red;") else: self.widgets.agent_address.setStyleSheet("background-color:green") if s: self.config.globals["agent_address"] = str(s) self.input_validated = True def accept(self): #do some stuff first, like save if self.input_validated: self.config.save() QDialog.accept(self) else: QErrorMessage(self).showMessage("Bad input.. (somewhere?)")
class TextEditBaseWidget(QPlainTextEdit, BaseEditMixin): """Text edit base widget""" BRACE_MATCHING_SCOPE = ('sof', 'eof') CELL_SEPARATORS = ('#%%', '# %%', '# <codecell>') def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) BaseEditMixin.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.extra_selections_dict = {} self.connect(self, SIGNAL('textChanged()'), self.changed) self.connect(self, SIGNAL('cursorPositionChanged()'), self.cursor_position_changed) self.indent_chars = " "*4 # Code completion / calltips if parent is not None: mainwin = parent while not isinstance(mainwin, QMainWindow): mainwin = mainwin.parent() if mainwin is None: break if mainwin is not None: parent = mainwin self.completion_widget = CompletionWidget(self, parent) self.codecompletion_auto = False self.codecompletion_case = True self.codecompletion_single = False self.codecompletion_enter = False self.calltips = True self.calltip_position = None self.calltip_size = 600 self.calltip_font = QFont() self.completion_text = "" # Highlight current line color self.currentline_color = QColor(Qt.red).lighter(190) # Brace matching self.bracepos = None self.matched_p_color = QColor(Qt.green) self.unmatched_p_color = QColor(Qt.red) def setup_calltips(self, size=None, font=None): self.calltip_size = size self.calltip_font = font def setup_completion(self, size=None, font=None): self.completion_widget.setup_appearance(size, font) def set_indent_chars(self, indent_chars): self.indent_chars = indent_chars def set_palette(self, background, foreground): """ Set text editor palette colors: background color and caret (text cursor) color """ palette = QPalette() palette.setColor(QPalette.Base, background) palette.setColor(QPalette.Text, foreground) self.setPalette(palette) #------Line number area def get_linenumberarea_width(self): """Return line number area width""" # Implemented in CodeEditor, but needed here for completion widget return 0 #------Extra selections def get_extra_selections(self, key): return self.extra_selections_dict.get(key, []) def set_extra_selections(self, key, extra_selections): self.extra_selections_dict[key] = extra_selections def update_extra_selections(self): extra_selections = [] for key, extra in list(self.extra_selections_dict.items()): if key == 'current_line': # Python 3 compatibility (weird): current line has to be # highlighted first extra_selections = extra + extra_selections else: extra_selections += extra self.setExtraSelections(extra_selections) def clear_extra_selections(self, key): self.extra_selections_dict[key] = [] self.update_extra_selections() def changed(self): """Emit changed signal""" self.emit(SIGNAL('modificationChanged(bool)'), self.document().isModified()) #------Highlight current line def highlight_current_line(self): """Highlight current line""" selection = QTextEdit.ExtraSelection() # selection.format.setProperty(QTextFormat.FullWidthSelection, # QVariant(True)) selection.format.setBackground(self.currentline_color) selection.cursor = self.textCursor() selection.cursor.clearSelection() self.set_extra_selections('current_line', [selection]) self.update_extra_selections() def unhighlight_current_line(self): """Unhighlight current line""" self.clear_extra_selections('current_line') #------Brace matching def find_brace_match(self, position, brace, forward): start_pos, end_pos = self.BRACE_MATCHING_SCOPE if forward: bracemap = {'(': ')', '[': ']', '{': '}'} text = self.get_text(position, end_pos) i_start_open = 1 i_start_close = 1 else: bracemap = {')': '(', ']': '[', '}': '{'} text = self.get_text(start_pos, position) i_start_open = len(text)-1 i_start_close = len(text)-1 while True: if forward: i_close = text.find(bracemap[brace], i_start_close) else: i_close = text.rfind(bracemap[brace], 0, i_start_close+1) if i_close > -1: if forward: i_start_close = i_close+1 i_open = text.find(brace, i_start_open, i_close) else: i_start_close = i_close-1 i_open = text.rfind(brace, i_close, i_start_open+1) if i_open > -1: if forward: i_start_open = i_open+1 else: i_start_open = i_open-1 else: # found matching brace if forward: return position+i_close else: return position-(len(text)-i_close) else: # no matching brace return def __highlight(self, positions, color=None, cancel=False): if cancel: self.clear_extra_selections('brace_matching') return extra_selections = [] for position in positions: if position > self.get_position('eof'): return selection = QTextEdit.ExtraSelection() selection.format.setBackground(color) selection.cursor = self.textCursor() selection.cursor.clearSelection() selection.cursor.setPosition(position) selection.cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) extra_selections.append(selection) self.set_extra_selections('brace_matching', extra_selections) self.update_extra_selections() def cursor_position_changed(self): """Brace matching""" if self.bracepos is not None: self.__highlight(self.bracepos, cancel=True) self.bracepos = None cursor = self.textCursor() if cursor.position() == 0: return cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) text = to_text_string(cursor.selectedText()) pos1 = cursor.position() if text in (')', ']', '}'): pos2 = self.find_brace_match(pos1, text, forward=False) elif text in ('(', '[', '{'): pos2 = self.find_brace_match(pos1, text, forward=True) else: return if pos2 is not None: self.bracepos = (pos1, pos2) self.__highlight(self.bracepos, color=self.matched_p_color) else: self.bracepos = (pos1,) self.__highlight(self.bracepos, color=self.unmatched_p_color) #-----Widget setup and options def set_codecompletion_auto(self, state): """Set code completion state""" self.codecompletion_auto = state def set_codecompletion_case(self, state): """Case sensitive completion""" self.codecompletion_case = state self.completion_widget.case_sensitive = state def set_codecompletion_single(self, state): """Show single completion""" self.codecompletion_single = state self.completion_widget.show_single = state def set_codecompletion_enter(self, state): """Enable Enter key to select completion""" self.codecompletion_enter = state self.completion_widget.enter_select = state def set_calltips(self, state): """Set calltips state""" self.calltips = state def set_wrap_mode(self, mode=None): """ Set wrap mode Valid *mode* values: None, 'word', 'character' """ if mode == 'word': wrap_mode = QTextOption.WrapAtWordBoundaryOrAnywhere elif mode == 'character': wrap_mode = QTextOption.WrapAnywhere else: wrap_mode = QTextOption.NoWrap self.setWordWrapMode(wrap_mode) #------Reimplementing Qt methods def copy(self): """ Reimplement Qt method Copy text to clipboard with correct EOL chars """ QApplication.clipboard().setText(self.get_selected_text()) #------Text: get, set, ... def get_selection_as_executable_code(self): """Return selected text as a processed text, to be executable in a Python/IPython interpreter""" ls = self.get_line_separator() _indent = lambda line: len(line)-len(line.lstrip()) line_from, line_to = self.get_selection_bounds() text = self.get_selected_text() if not text: return lines = text.split(ls) if len(lines) > 1: # Multiline selection -> eventually fixing indentation original_indent = _indent(self.get_text_line(line_from)) text = (" "*(original_indent-_indent(lines[0])))+text # If there is a common indent to all lines, find it. # Moving from bottom line to top line ensures that blank # lines inherit the indent of the line *below* it, # which is the desired behavior. min_indent = 999 current_indent = 0 lines = text.split(ls) for i in range(len(lines)-1, -1, -1): line = lines[i] if line.strip(): current_indent = _indent(line) min_indent = min(current_indent, min_indent) else: lines[i] = ' ' * current_indent if min_indent: lines = [line[min_indent:] for line in lines] # Remove any leading whitespace or comment lines # since they confuse the reserved word detector that follows below first_line = lines[0].lstrip() while first_line == '' or first_line[0] == '#': lines.pop(0) first_line = lines[0].lstrip() # Add an EOL character after indentation blocks that start with some # Python reserved words, so that it gets evaluated automatically # by the console varname = re.compile('[a-zA-Z0-9_]*') # matches valid variable names maybe = False nextexcept = () for n, line in enumerate(lines): if not _indent(line): word = varname.match(line).group() if maybe and word not in nextexcept: lines[n-1] += ls maybe = False if word: if word in ('def', 'for', 'while', 'with', 'class'): maybe = True nextexcept = () elif word == 'if': maybe = True nextexcept = ('elif', 'else') elif word == 'try': maybe = True nextexcept = ('except', 'finally') if maybe: if lines[-1].strip() == '': lines[-1] += ls else: lines.append(ls) return ls.join(lines) def get_cell_as_executable_code(self): """Return cell contents as executable code""" start_pos, end_pos = self.__save_selection() self.select_current_cell() text = self.get_selection_as_executable_code() self.__restore_selection(start_pos, end_pos) return text def is_cell_separator(self, cursor=None, block=None): """Return True if cursor (or text block) is on a block separator""" assert cursor is not None or block is not None if cursor is not None: cursor0 = QTextCursor(cursor) cursor0.select(QTextCursor.BlockUnderCursor) text = to_text_string(cursor0.selectedText()) else: text = to_text_string(block.text()) return text.lstrip().startswith(self.CELL_SEPARATORS) def select_current_cell(self): """Select cell under cursor cell = group of lines separated by CELL_SEPARATORS""" cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfBlock) cur_pos = prev_pos = cursor.position() # Moving to the next line that is not a separator, if we are # exactly at one of them while self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return # If not, move backwards to find the previous separator while not self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.PreviousBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: if self.is_cell_separator(cursor): return else: break cursor.setPosition(prev_pos) cell_at_file_start = cursor.atStart() # Once we find it (or reach the beginning of the file) # move to the next separator (or the end of the file) # so we can grab the cell contents while not self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cur_pos = cursor.position() if cur_pos == prev_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) break prev_pos = cur_pos # Do nothing if we moved from beginning to end without # finding a separator if cell_at_file_start and cursor.atEnd(): return self.setTextCursor(cursor) def go_to_next_cell(self): """Go to the next cell of lines""" cursor = self.textCursor() cursor.movePosition(QTextCursor.NextBlock) cur_pos = prev_pos = cursor.position() while not self.is_cell_separator(cursor): # Moving to the next code cell cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return self.setTextCursor(cursor) def get_line_count(self): """Return document total line number""" return self.blockCount() def __save_selection(self): """Save current cursor selection and return position bounds""" cursor = self.textCursor() return cursor.selectionStart(), cursor.selectionEnd() def __restore_selection(self, start_pos, end_pos): """Restore cursor selection from position bounds""" cursor = self.textCursor() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) self.setTextCursor(cursor) def __duplicate_line_or_selection(self, after_current_line=True): """Duplicate current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() start_pos, end_pos = self.__save_selection() if to_text_string(cursor.selectedText()): cursor.setPosition(end_pos) # Check if end_pos is at the start of a block: if so, starting # changes from the previous block cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor) if not to_text_string(cursor.selectedText()): cursor.movePosition(QTextCursor.PreviousBlock) end_pos = cursor.position() cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) while cursor.position() <= end_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) if cursor.atEnd(): cursor_temp = QTextCursor(cursor) cursor_temp.clearSelection() cursor_temp.insertText(self.get_line_separator()) break cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) text = cursor.selectedText() cursor.clearSelection() if not after_current_line: # Moving cursor before current line/selected text cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) start_pos += len(text) end_pos += len(text) cursor.insertText(text) cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) def duplicate_line(self): """ Duplicate current line or selected text Paste the duplicated text *after* the current line/selected text """ self.__duplicate_line_or_selection(after_current_line=True) def copy_line(self): """ Copy current line or selected text Paste the duplicated text *before* the current line/selected text """ self.__duplicate_line_or_selection(after_current_line=False) def __move_line_or_selection(self, after_current_line=True): """Move current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() start_pos, end_pos = self.__save_selection() if to_text_string(cursor.selectedText()): # Check if start_pos is at the start of a block cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) start_pos = cursor.position() cursor.setPosition(end_pos) # Check if end_pos is at the start of a block: if so, starting # changes from the previous block cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor) if to_text_string(cursor.selectedText()): cursor.movePosition(QTextCursor.NextBlock) end_pos = cursor.position() else: cursor.movePosition(QTextCursor.StartOfBlock) start_pos = cursor.position() cursor.movePosition(QTextCursor.NextBlock) end_pos = cursor.position() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) sel_text = to_text_string(cursor.selectedText()) cursor.removeSelectedText() if after_current_line: text = to_text_string(cursor.block().text()) start_pos += len(text)+1 end_pos += len(text)+1 cursor.movePosition(QTextCursor.NextBlock) else: cursor.movePosition(QTextCursor.PreviousBlock) text = to_text_string(cursor.block().text()) start_pos -= len(text)+1 end_pos -= len(text)+1 cursor.insertText(sel_text) cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) def move_line_up(self): """Move up current line or selected text""" self.__move_line_or_selection(after_current_line=False) def move_line_down(self): """Move down current line or selected text""" self.__move_line_or_selection(after_current_line=True) def extend_selection_to_complete_lines(self): """Extend current selection to complete lines""" cursor = self.textCursor() start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) if cursor.atBlockStart(): cursor.movePosition(QTextCursor.PreviousBlock, QTextCursor.KeepAnchor) cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) self.setTextCursor(cursor) def delete_line(self): """Delete current line""" cursor = self.textCursor() if self.has_selected_text(): self.extend_selection_to_complete_lines() start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd() cursor.setPosition(start_pos) else: start_pos = end_pos = cursor.position() cursor.beginEditBlock() cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) while cursor.position() <= end_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) if cursor.atEnd(): break cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.endEditBlock() self.ensureCursorVisible() #------Code completion / Calltips def show_calltip(self, title, text, color='#2D62FF', at_line=None, at_position=None): """Show calltip""" if text is None or len(text) == 0: return # Saving cursor position: if at_position is None: at_position = self.get_position('cursor') self.calltip_position = at_position # Preparing text: weight = 'bold' if self.calltip_font.bold() else 'normal' size = self.calltip_font.pointSize() family = self.calltip_font.family() format1 = '<div style=\'font-size: %spt; color: %s\'>' % (size, color) format2 = '<hr><div style=\'font-family: "%s"; font-size: %spt; font-weight: %s\'>' % (family, size, weight) if isinstance(text, list): text = "\n ".join(text) text = text.replace('\n', '<br>') if len(text) > self.calltip_size: text = text[:self.calltip_size] + " ..." tiptext = format1 + ('<b>%s</b></div>' % title) \ + format2 + text + "</div>" # Showing tooltip at cursor position: cx, cy = self.get_coordinates('cursor') if at_line is not None: cx = 5 cursor = QTextCursor(self.document().findBlockByNumber(at_line-1)) cy = self.cursorRect(cursor).top() point = self.mapToGlobal(QPoint(cx, cy)) point.setX(point.x()+self.get_linenumberarea_width()) QToolTip.showText(point, tiptext) def hide_tooltip_if_necessary(self, key): """Hide calltip when necessary""" try: calltip_char = self.get_character(self.calltip_position) before = self.is_cursor_before(self.calltip_position, char_offset=1) other = key in (Qt.Key_ParenRight, Qt.Key_Period, Qt.Key_Tab) if calltip_char not in ('?', '(') or before or other: QToolTip.hideText() except (IndexError, TypeError): QToolTip.hideText() def show_completion_widget(self, textlist, automatic=True): """Show completion widget""" self.completion_widget.show_list(textlist, automatic=automatic) def hide_completion_widget(self): """Hide completion widget""" self.completion_widget.hide() def show_completion_list(self, completions, completion_text="", automatic=True): """Display the possible completions""" if len(completions) == 0 or completions == [completion_text]: return self.completion_text = completion_text # Sorting completion list (entries starting with underscore are # put at the end of the list): underscore = set([comp for comp in completions if comp.startswith('_')]) completions = sorted(set(completions)-underscore, key=str_lower)+\ sorted(underscore, key=str_lower) self.show_completion_widget(completions, automatic=automatic) def select_completion_list(self): """Completion list is active, Enter was just pressed""" self.completion_widget.item_selected() def insert_completion(self, text): if text: cursor = self.textCursor() cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, len(self.completion_text)) cursor.removeSelectedText() self.insert_text(text) def is_completion_widget_visible(self): """Return True is completion list widget is visible""" return self.completion_widget.isVisible() #------Standard keys def stdkey_clear(self): if not self.has_selected_text(): self.moveCursor(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) self.remove_selected_text() def stdkey_backspace(self): if not self.has_selected_text(): self.moveCursor(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) self.remove_selected_text() def __get_move_mode(self, shift): return QTextCursor.KeepAnchor if shift else QTextCursor.MoveAnchor def stdkey_up(self, shift): self.moveCursor(QTextCursor.Up, self.__get_move_mode(shift)) def stdkey_down(self, shift): self.moveCursor(QTextCursor.Down, self.__get_move_mode(shift)) def stdkey_tab(self): self.insert_text(self.indent_chars) def stdkey_home(self, shift, ctrl, prompt_pos=None): """Smart HOME feature: cursor is first moved at indentation position, then at the start of the line""" move_mode = self.__get_move_mode(shift) if ctrl: self.moveCursor(QTextCursor.Start, move_mode) else: cursor = self.textCursor() if prompt_pos is None: start_position = self.get_position('sol') else: start_position = self.get_position(prompt_pos) text = self.get_text(start_position, 'eol') indent_pos = start_position+len(text)-len(text.lstrip()) if cursor.position() != indent_pos: cursor.setPosition(indent_pos, move_mode) else: cursor.setPosition(start_position, move_mode) self.setTextCursor(cursor) def stdkey_end(self, shift, ctrl): move_mode = self.__get_move_mode(shift) if ctrl: self.moveCursor(QTextCursor.End, move_mode) else: self.moveCursor(QTextCursor.EndOfBlock, move_mode) def stdkey_pageup(self): pass def stdkey_pagedown(self): pass def stdkey_escape(self): pass #----Qt Events def mousePressEvent(self, event): """Reimplement Qt method""" if os.name != 'posix' and event.button() == Qt.MidButton: self.setFocus() event = QMouseEvent(QEvent.MouseButtonPress, event.pos(), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier) QPlainTextEdit.mousePressEvent(self, event) QPlainTextEdit.mouseReleaseEvent(self, event) self.paste() else: QPlainTextEdit.mousePressEvent(self, event) def focusInEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) self.emit(SIGNAL("focus_in()")) QPlainTextEdit.focusInEvent(self, event) def focusOutEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) QPlainTextEdit.focusOutEvent(self, event) def wheelEvent(self, event): """Reimplemented to emit zoom in/out signals when Ctrl is pressed""" # This feature is disabled on MacOS, see Issue 1510: # http://code.google.com/p/spyderlib/issues/detail?id=1510 if sys.platform != 'darwin': if event.modifiers() & Qt.ControlModifier: if event.delta() < 0: self.emit(SIGNAL("zoom_out()")) elif event.delta() > 0: self.emit(SIGNAL("zoom_in()")) return QPlainTextEdit.wheelEvent(self, event)
class Framework(QObject): X_RAFT_ID = 'X-RAFT-ID' def __init__(self, parent = None): QObject.__init__(self, parent) self._global_cookie_jar = InMemoryCookieJar(self, self) self._db = None self._contentExtractor = None self._networkAccessManager = None self._scopeController = None self._scopeConfig = None self._requestResponseFactory = None self.zoom_size = 0 self.base_font = QFont() self.monospace_font = QFont('Courier New') self.monospace_font.setFixedPitch(True) self.monospace_font.setStyleHint(QFont.TypeWriter) pointSize = self.monospace_font.pointSize() basePointSize = self.base_font.pointSize() if pointSize <= basePointSize: pointSize = basePointSize + 2 self.monospace_font.setPointSize(pointSize) self.python_code_font = QFont() self.python_code_font.setPointSize(pointSize) self.home_dir = QDir.toNativeSeparators(QDir.homePath()) self.raft_dir = self.create_raft_directory(self.home_dir, '.raft') self.user_db_dir = self.create_raft_directory(self.raft_dir, 'db') self.user_data_dir = self.create_raft_directory(self.raft_dir, 'data') self.user_analyzer_dir = self.create_raft_directory(self.raft_dir, 'analyzers') self.user_web_path = self.create_raft_directory(self.raft_dir, 'web') self.web_db_path = self.user_web_path # TODO: there may be a Qt way to give executable path as well self._executable_path = os.path.abspath(os.path.dirname(sys.argv[0])) self._data_dir = os.path.join(self._executable_path, 'data') self._analyzer_dir = os.path.join(self._executable_path, 'analyzers') self._raft_config_cache = {} # configuration defaults self._default_useragent = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1' # callbacks self._open_url_in_browser = None self._open_content_in_browser = None def create_raft_directory(self, basepath, dirname): dirtarget = os.path.join(basepath, dirname) if not os.path.exists(dirtarget): os.mkdir(dirtarget) return dirtarget def useragent(self): if self._db is None: return self._default_useragent if self.get_raft_config_value('browser_custom_user_agent', bool, False): return self.get_raft_config_value('browser_user_agent_value', str, self._default_useragent) else: return self._default_useragent def default_useragent(self): return self._default_useragent def getDB(self): if self._db is None: raise Exception('database is not initialized') return self._db def setDB(self, db, dbname): if self._db is not None: raise Exception('database is already initialized') self._db = db self._db_uuid = self._db.get_db_uuid() self.web_db_path = self.create_raft_directory(self.user_web_path, self._db_uuid) self._raft_config_cache = {} self.emit(SIGNAL('raftConfigPopulated()')) self.emit(SIGNAL('databaseAttached()')) def closeDB(self): self.emit(SIGNAL('databaseDetached()')) self._db = None def subscribe_database_events(self, attach_callback, detach_callback): QObject.connect(self, SIGNAL('databaseAttached()'), attach_callback, Qt.DirectConnection) QObject.connect(self, SIGNAL('databaseDetached()'), detach_callback, Qt.DirectConnection) # if database is already available, invoke attach callback directly if self._db is not None: attach_callback() def get_temp_db_filename(self): # default filename is temp.raftdb return os.path.join(self.user_db_dir, 'temp.raftdb') def get_web_db_path(self): return self.web_db_path def get_user_home_dir(self): return self.home_dir def get_analyzer_paths(self): return [self._analyzer_dir, self.user_analyzer_dir] def get_data_dir(self): return self._data_dir def getContentExtractor(self): return self._contentExtractor def setContentExtractor(self, contentExtractor): self._contentExtractor = contentExtractor def getRequestResponseFactory(self): return self._requestResponseFactory def setRequestResponseFactory(self, requestResponseFactory): self._requestResponseFactory = requestResponseFactory def getScopeController(self): return self._scopeController def setScopeController(self, scopeController): self._scopeController = scopeController def getSpiderConfig(self): return self._spiderConfig def setSpiderConfig(self, spiderConfig): self._spiderConfig = spiderConfig def getNetworkAccessManager(self): return self._networkAccessManager def setNetworkAccessManager(self, networkAccessManager): self._networkAccessManager = networkAccessManager def subscribe_response_data_added(self, callback): QObject.connect(self, SIGNAL('responseDataAdded()'), callback, Qt.DirectConnection) def signal_response_data_added(self): self.emit(SIGNAL('responseDataAdded()')) QThread.yieldCurrentThread() def subscribe_zoom_in(self, callback): QObject.connect(self, SIGNAL('zoomIn()'), callback, Qt.DirectConnection) def unsubscribe_zoom_in(self, callback): QObject.disconnect(self, SIGNAL('zoomIn()'), callback) def get_zoom_size(self): return self.zoom_size def signal_zoom_in(self): self.zoom_size += 1 self.emit(SIGNAL('zoomIn()')) def subscribe_zoom_out(self, callback): QObject.connect(self, SIGNAL('zoomOut()'), callback, Qt.DirectConnection) def unsubscribe_zoom_out(self, callback): QObject.disconnect(self, SIGNAL('zoomOut()'), callback) def signal_zoom_out(self): self.zoom_size -= 1 self.emit(SIGNAL('zoomOut()')) def get_font(self): return self.base_font def get_monospace_font(self): return self.monospace_font def get_python_code_font(self): return self.python_code_font def subscribe_responses_cleared(self, callback): QObject.connect(self, SIGNAL('responsesCleared()'), callback, Qt.DirectConnection) def signal_responses_cleared(self): self.emit(SIGNAL('responsesCleared()')) def set_global_cookie_jar(self, cookie_jar): self._global_cookie_jar = cookie_jar def get_global_cookie_jar(self): return self._global_cookie_jar def signal_cookie_jar_updated(self): self.emit(SIGNAL('cookieJarUpdated()')) def import_raw_cookie_list(self, raw_cookie_list): cookieJar = self.get_global_cookie_jar() # merge cookies cookie_list = cookieJar.allCookies() for raw_cookie in raw_cookie_list: cookies = QNetworkCookie.parseCookies(raw_cookie) for cookie in cookies: if cookie not in cookie_list: cookie_list.append(cookie) cookieJar.setAllCookies(cookie_list) self.signal_cookie_jar_updated() def subscribe_cookie_jar_updated(self, callback): QObject.connect(self, SIGNAL('cookieJarUpdated()'), callback, Qt.DirectConnection) def subscribe_raft_config_populated(self, callback): QObject.connect(self, SIGNAL('raftConfigPopulated()'), callback, Qt.DirectConnection) if self._db is not None: callback() def unsubscribe_raft_config_populated(self, callback): QObject.disconnect(self, SIGNAL('raftConfigPopulated()'), callback) def subscribe_raft_config_updated(self, callback): QObject.connect(self, SIGNAL('raftConfigUpdated(QString, QVariant)'), callback, Qt.DirectConnection) def unsubscribe_raft_config_updated(self, callback): QObject.disconnect(self, SIGNAL('raftConfigUpdated(QString, QVariant)'), callback) def set_raft_config_value(self, name, value): if name in self._raft_config_cache: if str(value) == str(self._raft_config_cache[name]): return cursor = self._db.allocate_thread_cursor() self._db.set_config_value(cursor, 'RAFT', name, value) cursor.close() self._db.release_thread_cursor(cursor) self._raft_config_cache[name] = value self.emit(SIGNAL('raftConfigUpdated(QString, QVariant)'), name, value) def get_raft_config_value(self, name, rtype = str, default_value = None): if name in self._raft_config_cache: value = self._raft_config_cache[name] if value is not None: return rtype(value) else: return default_value cursor = self._db.allocate_thread_cursor() value = self._db.get_config_value(cursor, 'RAFT', name, rtype, default_value) cursor.close() self._db.release_thread_cursor(cursor) self._raft_config_cache[name] = value return value def set_config_value(self, component, name, value): if 'RAFT' == component.upper(): self.set_raft_config_value(name, value) else: cursor = self._db.allocate_thread_cursor() self._db.set_config_value(cursor, component, name, value) cursor.close() self._db.release_thread_cursor(cursor) def get_config_value(self, component, name, rtype = str, default_value = None): # TODO: implement config cache if 'RAFT' == component.upper(): return self.get_raft_config_value(name, rtype, default_value) else: cursor = self._db.allocate_thread_cursor() value = self._db.get_config_value(cursor, component, name, rtype, default_value) cursor.close() self._db.release_thread_cursor(cursor) return value def clear_config_value(self, component, name=None): cursor = self._db.allocate_thread_cursor() value = self._db.clear_config_value(cursor, component, name) cursor.close() self._db.release_thread_cursor(cursor) @functools.lru_cache(maxsize=16, typed=False) def get_request_response(self, response_id): return self._requestResponseFactory.fill(response_id) def report_implementation_issue(self, message): self.send_log_message('INTERNAL ERROR', '\n'.join(message)) print(('INTERNAL ERROR:\n%s' % message)) def report_implementation_error(self, exc): message = traceback.format_exception(type(exc), exc, exc.__traceback__) self.send_log_message('INTERNAL ERROR', '\n'.join(message)) print(('INTERNAL ERROR:\n%s' % message)) def report_exception(self, exc): message = traceback.format_exception(type(exc), exc, exc.__traceback__) self.send_log_message('EXCEPTION', '\n'.join(message)) print(('EXCEPTION:\n%s' % message)) def log_warning(self, msg): self.send_log_message('WARNING', msg) print(('WARNING', msg)) def console_log(self, msg): self.send_log_message('LOG', msg) print(msg) def debug_log(self, msg): self.send_log_message('DEBUG', msg) print(('DEBUG', msg)) def subscribe_add_differ_response_id(self, callback): QObject.connect(self, SIGNAL('differAddResponseId(int)'), callback, Qt.DirectConnection) def send_response_id_to_differ(self, Id): # TODO: consider accepting cursor cursor = self._db.allocate_thread_cursor() if self._db.add_differ_item(cursor, int(Id)): self.emit(SIGNAL('differAddResponseId(int)'), int(Id)) cursor.close() self._db.release_thread_cursor(cursor) def send_response_list_to_differ(self, id_list): # TODO: consider accepting cursor cursor = self._db.allocate_thread_cursor() added_list = self._db.add_differ_list(cursor, id_list) # TODO: send as whole list? for Id in added_list: self.emit(SIGNAL('differAddResponseId(int)'), int(Id)) cursor.close() self._db.release_thread_cursor(cursor) def subscribe_populate_requester_response_id(self, callback): QObject.connect(self, SIGNAL('requesterPopulateResponseId(int)'), callback, Qt.DirectConnection) def subscribe_populate_bulk_requester_responses(self, callback): QObject.connect(self, SIGNAL('bulkRequesterPopulateResponses(QList<int>)'), callback, Qt.DirectConnection) def subscribe_populate_webfuzzer_response_id(self, callback): QObject.connect(self, SIGNAL('webfuzzerPopulateResponseId(int)'), callback, Qt.DirectConnection) def subscribe_populate_domfuzzer_response_id(self, callback): QObject.connect(self, SIGNAL('domfuzzerPopulateResponseId(int)'), callback, Qt.DirectConnection) def subscribe_populate_domfuzzer_response_list(self, callback): QObject.connect(self, SIGNAL('domfuzzerPopulateResponseList(QStringList)'), callback, Qt.DirectConnection) def subscribe_populate_spider_response_id(self, callback): QObject.connect(self, SIGNAL('spiderPopulateResponseId(int)'), callback, Qt.DirectConnection) def subscribe_populate_spider_response_list(self, callback): QObject.connect(self, SIGNAL('spiderPopulateResponseList(QStringList)'), callback, Qt.DirectConnection) def send_response_id_to_requester(self, Id): self.emit(SIGNAL('requesterPopulateResponseId(int)'), int(Id)) def send_responses_to_bulk_requester(self, ids): self.emit(SIGNAL('bulkRequesterPopulateResponses(QList<int>)'), ids) def send_response_id_to_webfuzzer(self, Id): self.emit(SIGNAL('webfuzzerPopulateResponseId(int)'), int(Id)) def send_response_id_to_domfuzzer(self, Id): self.emit(SIGNAL('domfuzzerPopulateResponseId(int)'), int(Id)) def send_response_list_to_domfuzzer(self, id_list): self.emit(SIGNAL('domfuzzerPopulateResponseList(QStringList)'), id_list) def send_response_id_to_spider(self, Id): self.emit(SIGNAL('spiderPopulateResponseId(int)'), int(Id)) def send_response_list_to_spider(self, id_list): self.emit(SIGNAL('spiderPopulateResponseList(QStringList)'), id_list) def subscribe_add_sequence_builder_response_id(self, callback): QObject.connect(self, SIGNAL('sequenceBuilderAddResponseId(int)'), callback, Qt.DirectConnection) def send_response_id_to_sequence_builder(self, Id): # TODO: consider accepting cursor cursor = self._db.allocate_thread_cursor() if self._db.add_sequence_builder_manual_item(cursor, int(Id)): self.emit(SIGNAL('sequenceBuilderAddResponseId(int)'), int(Id)) cursor.close() self._db.release_thread_cursor(cursor) def subscribe_sequences_changed(self, callback): QObject.connect(self, SIGNAL('sequencesChanged()'), callback, Qt.DirectConnection) def signal_sequences_changed(self): self.emit(SIGNAL('sequencesChanged()')) def subscribe_populate_tester_csrf(self, callback): QObject.connect(self, SIGNAL('testerPopulateCSRFResponseId(int)'), callback, Qt.DirectConnection) def send_to_tester_csrf(self, Id): self.emit(SIGNAL('testerPopulateCSRFResponseId(int)'), int(Id)) def subscribe_populate_tester_click_jacking(self, callback): QObject.connect(self, SIGNAL('testerPopulateClickJackingResponseId(int)'), callback, Qt.DirectConnection) def send_to_tester_click_jacking(self, Id): self.emit(SIGNAL('testerPopulateClickJackingResponseId(int)'), int(Id)) def subscribe_log_events(self, callback): QObject.connect(self, SIGNAL('logMessageReceived(QString, QString)'), callback, Qt.DirectConnection) def send_log_message(self, message_type, message): self.emit(SIGNAL('logMessageReceived(QString, QString)'), message_type, message) def register_browser_openers(self, open_url, open_content): if self._open_url_in_browser is None: self._open_url_in_browser = open_url QObject.connect(self, SIGNAL('openUrlInBrowser(QString)'), self._open_url_in_browser, Qt.DirectConnection) if self._open_content_in_browser is None: self._open_content_in_browser = open_content QObject.connect(self, SIGNAL('openContentInBrowser(QString, QByteArray, QString)'), self._open_content_in_browser, Qt.DirectConnection) def open_url_in_browser(self, url): self.emit(SIGNAL('openUrlInBrowser(QString)'), url) def open_content_in_browser(self, url, body, mimetype=''): if isinstance(body, str): data = body.encode('utf-8') # TODO: vary based on mimetype ? else: data = body self.emit(SIGNAL('openContentInBrowser(QString, QByteArray, QString)'), url, data, mimetype)
class QnoteroItemDelegate(QStyledItemDelegate): """Draws pretty result items""" def __init__(self, qnotero): """ Constructor Arguments: qnotero -- a Qnotero instance """ QStyledItemDelegate.__init__(self, qnotero) self.qnotero = qnotero self.boldFont = QFont() self.boldFont.setBold(True) self.regularFont = QFont() self.italicFont = QFont() self.italicFont.setItalic(True) self.tagFont = QFont() self.tagFont.setBold(True) self.tagFont.setPointSize(self.boldFont.pointSize() - 2) self.dy = QFontMetrics(self.boldFont) \ .size(Qt.TextSingleLine, "Dummy").height() \ *self.qnotero.theme.lineHeight() self.margin = 0.5*self.dy self._margin = 0.1*self.dy self.height = 5*self.dy+self._margin self.noPdfPixmap = self.qnotero.theme.pixmap("nopdf") self.pdfPixmap = self.qnotero.theme.pixmap("pdf") self.aboutPixmap = self.qnotero.theme.pixmap("about") self.notePixmap = self.qnotero.theme.pixmap("note") self.pixmapSize = self.pdfPixmap.height()+0.5*self.dy self.roundness = self.qnotero.theme.roundness() def sizeHint(self, option, index): """ Suggest a size for the widget Arguments: option -- a QStyleOptionView index -- a QModelIndex Returns: A QSize """ return QSize(0, self.height) def paint(self, painter, option, index): """ Draws the widget Arguments: painter -- a QPainter option -- a QStyleOptionView index -- a QModelIndex """ # Retrieve the data model = index.model() record = model.data(index) text = record.toString() zoteroItem = zoteroCache[unicode(text)] l = zoteroItem.full_format().split("\n") if zoteroItem.fulltext == None: pixmap = self.noPdfPixmap else: pixmap = self.pdfPixmap # Choose the colors self.palette = self.qnotero.ui.listWidgetResults.palette() if option.state & QStyle.State_MouseOver: background = self.palette.Highlight foreground = self.palette.HighlightedText _note = zoteroItem.get_note() if _note != None: self.qnotero.showNoteHint() else: self.qnotero.hideNoteHint() elif option.state & QStyle.State_Selected: background = self.palette.Dark foreground = self.palette.WindowText else: background = self.palette.Base foreground = self.palette.WindowText # Draw the frame _rect = option.rect.adjusted(self._margin, self._margin, \ -2*self._margin, -self._margin) pen = painter.pen() pen.setColor(self.palette.color(background)) painter.setPen(pen) painter.setBrush(self.palette.brush(background)) painter.drawRoundedRect(_rect, self.roundness, self.roundness) font = painter.font pen = painter.pen() pen.setColor(self.palette.color(foreground)) painter.setPen(pen) # Draw icon _rect = QRect(option.rect) _rect.moveBottom(_rect.bottom() + 0.5*self.dy) _rect.moveLeft(_rect.left() + 0.5*self.dy) _rect.setHeight(self.pixmapSize) _rect.setWidth(self.pixmapSize) painter.drawPixmap(_rect, pixmap) # Draw the text _rect = option.rect.adjusted(self.pixmapSize+self.dy, 0.5*self.dy, \ -self.dy, 0) f = [self.tagFont, self.italicFont, self.regularFont, \ self.boldFont] l.reverse() while len(l) > 0: s = l.pop() if len(f) > 0: painter.setFont(f.pop()) painter.drawText(_rect, Qt.AlignLeft, s) _rect = _rect.adjusted(0, self.dy, 0, 0)
class CharMap(QWidget): """A widget displaying a table of characters.""" characterSelected = pyqtSignal(str) characterClicked = pyqtSignal(str) def __init__(self, parent=None): super(CharMap, self).__init__(parent) self._showToolTips = True self._showWhatsThis = True self._selected = -1 self._column_count = 32 self._square = 24 self._range = (0, 0) self._font = QFont() def setRange(self, first, last): self._range = (first, last) self._selected = -1 self.adjustSize() self.update() def range(self): return self._range def square(self): """Returns the width of one item (determined by font size).""" return self._square def select(self, charcode): """Selects the specified character (int or str).""" if not isinstance(charcode, int): charcode = ord(charcode) if not self._range[0] <= charcode <= self._range[1]: charcode = -1 if self._selected != charcode: self._selected = charcode self.characterSelected.emit(chr(charcode)) self.update() def character(self): """Returns the currently selected character, if any.""" if self._selected != -1: return chr(self._selected) def setDisplayFont(self, font): self._font.setFamily(font.family()) self.update() def displayFont(self): return QFont(self._font) def setDisplayFontSize(self, size): self._font.setPointSize(size) self._square = max(24, QFontMetrics(self._font).xHeight() * 3) self.adjustSize() self.update() def displayFontSize(self): return self._font.pointSize() def setDisplayFontSizeF(self, size): self._font.setPointSizeF(size) self._square = max(24, QFontMetrics(self._font).xHeight() * 3) self.adjustSize() self.update() def displayFontSizeF(self): return self._font.pointSizeF() def setColumnCount(self, count): """Sets how many columns should be used.""" count = max(1, count) self._column_count = count self.adjustSize() self.update() def columnCount(self): return self._column_count def sizeHint(self): return self.sizeForColumnCount(self._column_count) def paintEvent(self, ev): rect = ev.rect() s = self._square rows = range(rect.top() // s, rect.bottom() // s + 1) cols = range(rect.left() // s, rect.right() // s + 1) painter = QPainter(self) painter.setPen(QPen(self.palette().color(QPalette.Window))) painter.setFont(self._font) metrics = QFontMetrics(self._font) # draw characters on white tiles tile = self.palette().color(QPalette.Base) selected_tile = self.palette().color(QPalette.Highlight) selected_tile.setAlpha(96) selected_box = self.palette().color(QPalette.Highlight) text_pen = QPen(self.palette().text()) disabled_pen = QPen(self.palette().color(QPalette.Disabled, QPalette.Text)) selection_pen = QPen(selected_box) for row in rows: for col in cols: char = row * self._column_count + col + self._range[0] if char > self._range[1]: break printable = self.isprint(char) painter.setClipRect(col * s, row * s, s, s) if char == self._selected: painter.fillRect(col * s + 1, row * s + 1, s - 2, s - 2, selected_tile) painter.setPen(selection_pen) painter.drawRect(col * s, row * s, s - 1, s - 1) elif printable: painter.fillRect(col * s + 1, row * s + 1, s - 2, s - 2, tile) painter.setPen(text_pen if printable else disabled_pen) t = chr(char) x = col * s + s // 2 - metrics.width(t) // 2 y = row * s + 4 + metrics.ascent() painter.drawText(x, y, t) else: continue break def sizeForColumnCount(self, count): """Returns the size the widget would have in a certain column count. This can be used in e.g. a resizable scroll area. """ first, last = self._range rows = ((last - first) // count) + 1 return QSize(count, rows) * self._square def columnCountForWidth(self, width): """Returns the number of columns that would fit into the given width.""" return width // self._square def mousePressEvent(self, ev): charcode = self.charcodeAt(ev.pos()) if charcode != -1 and self.isprint(charcode): self.select(charcode) if ev.button() != Qt.RightButton: self.characterClicked.emit(chr(charcode)) def charcodeRect(self, charcode): """Returns the rectangular box around the given charcode, if any.""" if self._range[0] <= charcode <= self._range[1]: row, col = divmod(charcode - self._range[0], self._column_count) s = self._square return QRect(col * s, row * s, s, s) def charcodeAt(self, position): row = position.y() // self._square col = position.x() // self._square if col <= self._column_count: charcode = self._range[0] + row * self._column_count + col if charcode <= self._range[1]: return charcode return -1 def event(self, ev): if ev.type() == QEvent.ToolTip: if self._showToolTips: c = self.charcodeAt(ev.pos()) if c: text = self.getToolTipText(c) if text: rect = self.charcodeRect(c) QToolTip.showText(ev.globalPos(), text, self, rect) ev.accept() return True elif ev.type() == QEvent.QueryWhatsThis: if self._showWhatsThis: ev.accept() return True elif ev.type() == QEvent.WhatsThis: ev.accept() if self._showWhatsThis: c = self.charcodeAt(ev.pos()) text = self.getWhatsThisText(c) if c else None if text: QWhatsThis.showText(ev.globalPos(), text, self) else: QWhatsThis.leaveWhatsThisMode() return True return super(CharMap, self).event(ev) def getToolTipText(self, charcode): try: return unicodedata.name(chr(charcode)) except ValueError: pass def getWhatsThisText(self, charcode): try: name = unicodedata.name(chr(charcode)) except ValueError: return return whatsthis_html.format( self._font.family(), chr(charcode), name, charcode) def setShowToolTips(self, enabled): self._showToolTips = bool(enabled) def showToolTips(self): return self._showToolTips def setShowWhatsThis(self, enabled): self._showWhatsThis = bool(enabled) def showWhatsThis(self): return self._showWhatsThis def isprint(self, charcode): """Returns True if the given charcode is printable.""" return isprint(charcode)
class TextEditBaseWidget(QPlainTextEdit, BaseEditMixin): """Text edit base widget""" BRACE_MATCHING_SCOPE = ('sof', 'eof') CELL_SEPARATORS = ('#%%', '# %%', '# <codecell>') def __init__(self, parent=None): QPlainTextEdit.__init__(self, parent) BaseEditMixin.__init__(self) self.setAttribute(Qt.WA_DeleteOnClose) self.extra_selections_dict = {} self.connect(self, SIGNAL('textChanged()'), self.changed) self.connect(self, SIGNAL('cursorPositionChanged()'), self.cursor_position_changed) self.indent_chars = " " * 4 # Code completion / calltips if parent is not None: mainwin = parent while not isinstance(mainwin, QMainWindow): mainwin = mainwin.parent() if mainwin is None: break if mainwin is not None: parent = mainwin self.completion_widget = CompletionWidget(self, parent) self.codecompletion_auto = False self.codecompletion_case = True self.codecompletion_single = False self.codecompletion_enter = False self.calltips = True self.calltip_position = None self.calltip_size = 600 self.calltip_font = QFont() self.completion_text = "" # Highlight current line color self.currentline_color = QColor(Qt.red).lighter(190) # Brace matching self.bracepos = None self.matched_p_color = QColor(Qt.green) self.unmatched_p_color = QColor(Qt.red) def setup_calltips(self, size=None, font=None): self.calltip_size = size self.calltip_font = font def setup_completion(self, size=None, font=None): self.completion_widget.setup_appearance(size, font) def set_indent_chars(self, indent_chars): self.indent_chars = indent_chars def set_palette(self, background, foreground): """ Set text editor palette colors: background color and caret (text cursor) color """ palette = QPalette() palette.setColor(QPalette.Base, background) palette.setColor(QPalette.Text, foreground) self.setPalette(palette) #------Line number area def get_linenumberarea_width(self): """Return line number area width""" # Implemented in CodeEditor, but needed here for completion widget return 0 #------Extra selections def get_extra_selections(self, key): return self.extra_selections_dict.get(key, []) def set_extra_selections(self, key, extra_selections): self.extra_selections_dict[key] = extra_selections def update_extra_selections(self): extra_selections = [] for key, extra in list(self.extra_selections_dict.items()): if key == 'current_line': # Python 3 compatibility (weird): current line has to be # highlighted first extra_selections = extra + extra_selections else: extra_selections += extra self.setExtraSelections(extra_selections) def clear_extra_selections(self, key): self.extra_selections_dict[key] = [] self.update_extra_selections() def changed(self): """Emit changed signal""" self.emit(SIGNAL('modificationChanged(bool)'), self.document().isModified()) #------Highlight current line def highlight_current_line(self): """Highlight current line""" selection = QTextEdit.ExtraSelection() # selection.format.setProperty(QTextFormat.FullWidthSelection, # QVariant(True)) selection.format.setBackground(self.currentline_color) selection.cursor = self.textCursor() selection.cursor.clearSelection() self.set_extra_selections('current_line', [selection]) self.update_extra_selections() def unhighlight_current_line(self): """Unhighlight current line""" self.clear_extra_selections('current_line') #------Brace matching def find_brace_match(self, position, brace, forward): start_pos, end_pos = self.BRACE_MATCHING_SCOPE if forward: bracemap = {'(': ')', '[': ']', '{': '}'} text = self.get_text(position, end_pos) i_start_open = 1 i_start_close = 1 else: bracemap = {')': '(', ']': '[', '}': '{'} text = self.get_text(start_pos, position) i_start_open = len(text) - 1 i_start_close = len(text) - 1 while True: if forward: i_close = text.find(bracemap[brace], i_start_close) else: i_close = text.rfind(bracemap[brace], 0, i_start_close + 1) if i_close > -1: if forward: i_start_close = i_close + 1 i_open = text.find(brace, i_start_open, i_close) else: i_start_close = i_close - 1 i_open = text.rfind(brace, i_close, i_start_open + 1) if i_open > -1: if forward: i_start_open = i_open + 1 else: i_start_open = i_open - 1 else: # found matching brace if forward: return position + i_close else: return position - (len(text) - i_close) else: # no matching brace return def __highlight(self, positions, color=None, cancel=False): if cancel: self.clear_extra_selections('brace_matching') return extra_selections = [] for position in positions: if position > self.get_position('eof'): return selection = QTextEdit.ExtraSelection() selection.format.setBackground(color) selection.cursor = self.textCursor() selection.cursor.clearSelection() selection.cursor.setPosition(position) selection.cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) extra_selections.append(selection) self.set_extra_selections('brace_matching', extra_selections) self.update_extra_selections() def cursor_position_changed(self): """Brace matching""" if self.bracepos is not None: self.__highlight(self.bracepos, cancel=True) self.bracepos = None cursor = self.textCursor() if cursor.position() == 0: return cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) text = to_text_string(cursor.selectedText()) pos1 = cursor.position() if text in (')', ']', '}'): pos2 = self.find_brace_match(pos1, text, forward=False) elif text in ('(', '[', '{'): pos2 = self.find_brace_match(pos1, text, forward=True) else: return if pos2 is not None: self.bracepos = (pos1, pos2) self.__highlight(self.bracepos, color=self.matched_p_color) else: self.bracepos = (pos1, ) self.__highlight(self.bracepos, color=self.unmatched_p_color) #-----Widget setup and options def set_codecompletion_auto(self, state): """Set code completion state""" self.codecompletion_auto = state def set_codecompletion_case(self, state): """Case sensitive completion""" self.codecompletion_case = state self.completion_widget.case_sensitive = state def set_codecompletion_single(self, state): """Show single completion""" self.codecompletion_single = state self.completion_widget.show_single = state def set_codecompletion_enter(self, state): """Enable Enter key to select completion""" self.codecompletion_enter = state self.completion_widget.enter_select = state def set_calltips(self, state): """Set calltips state""" self.calltips = state def set_wrap_mode(self, mode=None): """ Set wrap mode Valid *mode* values: None, 'word', 'character' """ if mode == 'word': wrap_mode = QTextOption.WrapAtWordBoundaryOrAnywhere elif mode == 'character': wrap_mode = QTextOption.WrapAnywhere else: wrap_mode = QTextOption.NoWrap self.setWordWrapMode(wrap_mode) #------Reimplementing Qt methods def copy(self): """ Reimplement Qt method Copy text to clipboard with correct EOL chars """ QApplication.clipboard().setText(self.get_selected_text()) #------Text: get, set, ... def get_selection_as_executable_code(self): """Return selected text as a processed text, to be executable in a Python/IPython interpreter""" ls = self.get_line_separator() _indent = lambda line: len(line) - len(line.lstrip()) line_from, line_to = self.get_selection_bounds() text = self.get_selected_text() if not text: return lines = text.split(ls) if len(lines) > 1: # Multiline selection -> eventually fixing indentation original_indent = _indent(self.get_text_line(line_from)) text = (" " * (original_indent - _indent(lines[0]))) + text # If there is a common indent to all lines, find it. # Moving from bottom line to top line ensures that blank # lines inherit the indent of the line *below* it, # which is the desired behavior. min_indent = 999 current_indent = 0 lines = text.split(ls) for i in range(len(lines) - 1, -1, -1): line = lines[i] if line.strip(): current_indent = _indent(line) min_indent = min(current_indent, min_indent) else: lines[i] = ' ' * current_indent if min_indent: lines = [line[min_indent:] for line in lines] # Remove any leading whitespace or comment lines # since they confuse the reserved word detector that follows below first_line = lines[0].lstrip() while first_line == '' or first_line[0] == '#': lines.pop(0) first_line = lines[0].lstrip() # Add an EOL character after indentation blocks that start with some # Python reserved words, so that it gets evaluated automatically # by the console varname = re.compile('[a-zA-Z0-9_]*') # matches valid variable names maybe = False nextexcept = () for n, line in enumerate(lines): if not _indent(line): word = varname.match(line).group() if maybe and word not in nextexcept: lines[n - 1] += ls maybe = False if word: if word in ('def', 'for', 'while', 'with', 'class'): maybe = True nextexcept = () elif word == 'if': maybe = True nextexcept = ('elif', 'else') elif word == 'try': maybe = True nextexcept = ('except', 'finally') if maybe: if lines[-1].strip() == '': lines[-1] += ls else: lines.append(ls) return ls.join(lines) def get_cell_as_executable_code(self): """Return cell contents as executable code""" start_pos, end_pos = self.__save_selection() self.select_current_cell() text = self.get_selection_as_executable_code() self.__restore_selection(start_pos, end_pos) return text def is_cell_separator(self, cursor=None, block=None): """Return True if cursor (or text block) is on a block separator""" assert cursor is not None or block is not None if cursor is not None: cursor0 = QTextCursor(cursor) cursor0.select(QTextCursor.BlockUnderCursor) text = to_text_string(cursor0.selectedText()) else: text = to_text_string(block.text()) return text.lstrip().startswith(self.CELL_SEPARATORS) def select_current_cell(self): """Select cell under cursor cell = group of lines separated by CELL_SEPARATORS""" cursor = self.textCursor() cursor.movePosition(QTextCursor.StartOfBlock) cur_pos = prev_pos = cursor.position() # Moving to the next line that is not a separator, if we are # exactly at one of them while self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return # If not, move backwards to find the previous separator while not self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.PreviousBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: if self.is_cell_separator(cursor): return else: break cursor.setPosition(prev_pos) cell_at_file_start = cursor.atStart() # Once we find it (or reach the beginning of the file) # move to the next separator (or the end of the file) # so we can grab the cell contents while not self.is_cell_separator(cursor): cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cur_pos = cursor.position() if cur_pos == prev_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) break prev_pos = cur_pos # Do nothing if we moved from beginning to end without # finding a separator if cell_at_file_start and cursor.atEnd(): return self.setTextCursor(cursor) def go_to_next_cell(self): """Go to the next cell of lines""" cursor = self.textCursor() cursor.movePosition(QTextCursor.NextBlock) cur_pos = prev_pos = cursor.position() while not self.is_cell_separator(cursor): # Moving to the next code cell cursor.movePosition(QTextCursor.NextBlock) prev_pos = cur_pos cur_pos = cursor.position() if cur_pos == prev_pos: return self.setTextCursor(cursor) def get_line_count(self): """Return document total line number""" return self.blockCount() def __save_selection(self): """Save current cursor selection and return position bounds""" cursor = self.textCursor() return cursor.selectionStart(), cursor.selectionEnd() def __restore_selection(self, start_pos, end_pos): """Restore cursor selection from position bounds""" cursor = self.textCursor() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) self.setTextCursor(cursor) def __duplicate_line_or_selection(self, after_current_line=True): """Duplicate current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() start_pos, end_pos = self.__save_selection() if to_text_string(cursor.selectedText()): cursor.setPosition(end_pos) # Check if end_pos is at the start of a block: if so, starting # changes from the previous block cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor) if not to_text_string(cursor.selectedText()): cursor.movePosition(QTextCursor.PreviousBlock) end_pos = cursor.position() cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) while cursor.position() <= end_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) if cursor.atEnd(): cursor_temp = QTextCursor(cursor) cursor_temp.clearSelection() cursor_temp.insertText(self.get_line_separator()) break cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) text = cursor.selectedText() cursor.clearSelection() if not after_current_line: # Moving cursor before current line/selected text cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) start_pos += len(text) end_pos += len(text) cursor.insertText(text) cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) def duplicate_line(self): """ Duplicate current line or selected text Paste the duplicated text *after* the current line/selected text """ self.__duplicate_line_or_selection(after_current_line=True) def copy_line(self): """ Copy current line or selected text Paste the duplicated text *before* the current line/selected text """ self.__duplicate_line_or_selection(after_current_line=False) def __move_line_or_selection(self, after_current_line=True): """Move current line or selected text""" cursor = self.textCursor() cursor.beginEditBlock() start_pos, end_pos = self.__save_selection() if to_text_string(cursor.selectedText()): # Check if start_pos is at the start of a block cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) start_pos = cursor.position() cursor.setPosition(end_pos) # Check if end_pos is at the start of a block: if so, starting # changes from the previous block cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor) if to_text_string(cursor.selectedText()): cursor.movePosition(QTextCursor.NextBlock) end_pos = cursor.position() else: cursor.movePosition(QTextCursor.StartOfBlock) start_pos = cursor.position() cursor.movePosition(QTextCursor.NextBlock) end_pos = cursor.position() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) sel_text = to_text_string(cursor.selectedText()) cursor.removeSelectedText() if after_current_line: text = to_text_string(cursor.block().text()) start_pos += len(text) + 1 end_pos += len(text) + 1 cursor.movePosition(QTextCursor.NextBlock) else: cursor.movePosition(QTextCursor.PreviousBlock) text = to_text_string(cursor.block().text()) start_pos -= len(text) + 1 end_pos -= len(text) + 1 cursor.insertText(sel_text) cursor.endEditBlock() self.setTextCursor(cursor) self.__restore_selection(start_pos, end_pos) def move_line_up(self): """Move up current line or selected text""" self.__move_line_or_selection(after_current_line=False) def move_line_down(self): """Move down current line or selected text""" self.__move_line_or_selection(after_current_line=True) def extend_selection_to_complete_lines(self): """Extend current selection to complete lines""" cursor = self.textCursor() start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd() cursor.setPosition(start_pos) cursor.setPosition(end_pos, QTextCursor.KeepAnchor) if cursor.atBlockStart(): cursor.movePosition(QTextCursor.PreviousBlock, QTextCursor.KeepAnchor) cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) self.setTextCursor(cursor) def delete_line(self): """Delete current line""" cursor = self.textCursor() if self.has_selected_text(): self.extend_selection_to_complete_lines() start_pos, end_pos = cursor.selectionStart(), cursor.selectionEnd() cursor.setPosition(start_pos) else: start_pos = end_pos = cursor.position() cursor.beginEditBlock() cursor.setPosition(start_pos) cursor.movePosition(QTextCursor.StartOfBlock) while cursor.position() <= end_pos: cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) if cursor.atEnd(): break cursor.movePosition(QTextCursor.NextBlock, QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.endEditBlock() self.ensureCursorVisible() #------Code completion / Calltips def show_calltip(self, title, text, color='#2D62FF', at_line=None, at_position=None): """Show calltip""" if text is None or len(text) == 0: return # Saving cursor position: if at_position is None: at_position = self.get_position('cursor') self.calltip_position = at_position # Preparing text: weight = 'bold' if self.calltip_font.bold() else 'normal' size = self.calltip_font.pointSize() family = self.calltip_font.family() format1 = '<div style=\'font-size: %spt; color: %s\'>' % (size, color) format2 = '<hr><div style=\'font-family: "%s"; font-size: %spt; font-weight: %s\'>' % ( family, size, weight) if isinstance(text, list): text = "\n ".join(text) text = text.replace('\n', '<br>') if len(text) > self.calltip_size: text = text[:self.calltip_size] + " ..." tiptext = format1 + ('<b>%s</b></div>' % title) \ + format2 + text + "</div>" # Showing tooltip at cursor position: cx, cy = self.get_coordinates('cursor') if at_line is not None: cx = 5 cursor = QTextCursor(self.document().findBlockByNumber(at_line - 1)) cy = self.cursorRect(cursor).top() point = self.mapToGlobal(QPoint(cx, cy)) point.setX(point.x() + self.get_linenumberarea_width()) QToolTip.showText(point, tiptext) def hide_tooltip_if_necessary(self, key): """Hide calltip when necessary""" try: calltip_char = self.get_character(self.calltip_position) before = self.is_cursor_before(self.calltip_position, char_offset=1) other = key in (Qt.Key_ParenRight, Qt.Key_Period, Qt.Key_Tab) if calltip_char not in ('?', '(') or before or other: QToolTip.hideText() except (IndexError, TypeError): QToolTip.hideText() def show_completion_widget(self, textlist, automatic=True): """Show completion widget""" self.completion_widget.show_list(textlist, automatic=automatic) def hide_completion_widget(self): """Hide completion widget""" self.completion_widget.hide() def show_completion_list(self, completions, completion_text="", automatic=True): """Display the possible completions""" if len(completions) == 0 or completions == [completion_text]: return self.completion_text = completion_text # Sorting completion list (entries starting with underscore are # put at the end of the list): underscore = set( [comp for comp in completions if comp.startswith('_')]) completions = sorted(set(completions)-underscore, key=str_lower)+\ sorted(underscore, key=str_lower) self.show_completion_widget(completions, automatic=automatic) def select_completion_list(self): """Completion list is active, Enter was just pressed""" self.completion_widget.item_selected() def insert_completion(self, text): if text: cursor = self.textCursor() cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, len(self.completion_text)) cursor.removeSelectedText() self.insert_text(text) def is_completion_widget_visible(self): """Return True is completion list widget is visible""" return self.completion_widget.isVisible() #------Standard keys def stdkey_clear(self): if not self.has_selected_text(): self.moveCursor(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) self.remove_selected_text() def stdkey_backspace(self): if not self.has_selected_text(): self.moveCursor(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor) self.remove_selected_text() def __get_move_mode(self, shift): return QTextCursor.KeepAnchor if shift else QTextCursor.MoveAnchor def stdkey_up(self, shift): self.moveCursor(QTextCursor.Up, self.__get_move_mode(shift)) def stdkey_down(self, shift): self.moveCursor(QTextCursor.Down, self.__get_move_mode(shift)) def stdkey_tab(self): self.insert_text(self.indent_chars) def stdkey_home(self, shift, ctrl, prompt_pos=None): """Smart HOME feature: cursor is first moved at indentation position, then at the start of the line""" move_mode = self.__get_move_mode(shift) if ctrl: self.moveCursor(QTextCursor.Start, move_mode) else: cursor = self.textCursor() if prompt_pos is None: start_position = self.get_position('sol') else: start_position = self.get_position(prompt_pos) text = self.get_text(start_position, 'eol') indent_pos = start_position + len(text) - len(text.lstrip()) if cursor.position() != indent_pos: cursor.setPosition(indent_pos, move_mode) else: cursor.setPosition(start_position, move_mode) self.setTextCursor(cursor) def stdkey_end(self, shift, ctrl): move_mode = self.__get_move_mode(shift) if ctrl: self.moveCursor(QTextCursor.End, move_mode) else: self.moveCursor(QTextCursor.EndOfBlock, move_mode) def stdkey_pageup(self): pass def stdkey_pagedown(self): pass def stdkey_escape(self): pass #----Qt Events def mousePressEvent(self, event): """Reimplement Qt method""" if os.name != 'posix' and event.button() == Qt.MidButton: self.setFocus() event = QMouseEvent(QEvent.MouseButtonPress, event.pos(), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier) QPlainTextEdit.mousePressEvent(self, event) QPlainTextEdit.mouseReleaseEvent(self, event) self.paste() else: QPlainTextEdit.mousePressEvent(self, event) def focusInEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) self.emit(SIGNAL("focus_in()")) QPlainTextEdit.focusInEvent(self, event) def focusOutEvent(self, event): """Reimplemented to handle focus""" self.emit(SIGNAL("focus_changed()")) QPlainTextEdit.focusOutEvent(self, event) def wheelEvent(self, event): """Reimplemented to emit zoom in/out signals when Ctrl is pressed""" # This feature is disabled on MacOS, see Issue 1510: # http://code.google.com/p/spyderlib/issues/detail?id=1510 if sys.platform != 'darwin': if event.modifiers() & Qt.ControlModifier: if event.delta() < 0: self.emit(SIGNAL("zoom_out()")) elif event.delta() > 0: self.emit(SIGNAL("zoom_in()")) return QPlainTextEdit.wheelEvent(self, event)