def _set_data_window(self): self.file_display.setReadOnly(True) self._data = open(self.targetfile, 'rb').read() text = [] for i in range(0, len(self._data), CHARS_PER_LINE): line = self._data[i:i + CHARS_PER_LINE] text.append(hexdump(line)) self.file_display.setPlainText("\n".join(text)) self._cursor = QTextCursor(self.file_display.document()) self.file_display.setTextCursor(self._cursor)
def reformat_blocks(self, position, removed, added): doc = self.doc if doc is None or self.ignore_requests or not hasattr(self, 'state_map'): return block = doc.findBlock(position) if not block.isValid(): return start_cursor = QTextCursor(block) last_block = doc.findBlock(position + added + (1 if removed > 0 else 0)) if not last_block.isValid(): last_block = doc.lastBlock() end_cursor = QTextCursor(last_block) end_cursor.movePosition(end_cursor.EndOfBlock) self.requests.append((start_cursor, end_cursor)) QTimer.singleShot(0, self.do_one_block)
def do_test(self): selections = [] self.match_locs = [] if self.regex_valid(): text = unicode(self.preview.toPlainText()) regex = unicode(self.regex.text()) cursor = QTextCursor(self.preview.document()) extsel = QTextEdit.ExtraSelection() extsel.cursor = cursor extsel.format.setBackground(QBrush(Qt.yellow)) try: for match in re.finditer(regex, text): es = QTextEdit.ExtraSelection(extsel) es.cursor.setPosition(match.start(), QTextCursor.MoveAnchor) es.cursor.setPosition(match.end(), QTextCursor.KeepAnchor) selections.append(es) self.match_locs.append((match.start(), match.end())) except: pass self.preview.setExtraSelections(selections) if self.match_locs: self.next.setEnabled(True) self.previous.setEnabled(True) self.occurrences.setText(str(len(self.match_locs)))
def set_document(self, doc): old_doc = self.doc if old_doc is not None: old_doc.contentsChange.disconnect(self.reformat_blocks) c = QTextCursor(old_doc) c.beginEditBlock() blk = old_doc.begin() while blk.isValid(): blk.layout().clearAdditionalFormats() blk = blk.next() c.endEditBlock() self.doc = None if doc is not None: self.doc = doc doc.contentsChange.connect(self.reformat_blocks) self.rehighlight()
def reformat_blocks(self, position, removed, added): doc = self.doc if doc is None or self.ignore_requests or not hasattr( self, 'state_map'): return block = doc.findBlock(position) if not block.isValid(): return start_cursor = QTextCursor(block) last_block = doc.findBlock(position + added + (1 if removed > 0 else 0)) if not last_block.isValid(): last_block = doc.lastBlock() end_cursor = QTextCursor(last_block) end_cursor.movePosition(end_cursor.EndOfBlock) self.requests.append((start_cursor, end_cursor)) QTimer.singleShot(0, self.do_one_block)
def _tree_line_selected(self): if not self.data_tree.selectedItems(): return item = self.data_tree.selectedItems()[0] data = item.data(0, QtCore.Qt.UserRole) while data is None: item = self.data_tree.itemAbove(item) if item is None: break data = item.data(0, QtCore.Qt.UserRole) if data is not None: start, length = data self._clear_data_highlight() curs_start = to_display_pos(start) curs_end = to_display_pos(start + length) - 1 if self._cursor is None: self._cursor = QTextCursor(self.file_display.document()) self.file_display.setTextCursor(self._cursor) self._cursor.setPosition(curs_start) self._cursor.setPosition(curs_end, mode=self._cursor.KeepAnchor) highlight = QtGui.QTextCharFormat() highlight.setBackground(QtGui.QBrush(QtGui.QColor("red"))) self._cursor.mergeCharFormat(highlight)
def goto_loc(self, loc, operation=QTextCursor.Right, mode=QTextCursor.KeepAnchor, n=0): cursor = QTextCursor(self.preview.document()) cursor.setPosition(loc) if n: cursor.movePosition(operation, mode, n) self.preview.setTextCursor(cursor)
def copy_lines(self, lo, hi, cursor): ''' Copy specified lines from the syntax highlighted buffer into the destination cursor, preserving all formatting created by the syntax highlighter. ''' num = hi - lo if num > 0: block = self.findBlockByNumber(lo) while num > 0: num -= 1 cursor.insertText(block.text()) dest_block = cursor.block() c = QTextCursor(dest_block) for af in block.layout().additionalFormats(): start = dest_block.position() + af.start c.setPosition(start), c.setPosition(start + af.length, c.KeepAnchor) c.setCharFormat(af.format) cursor.insertBlock() cursor.setCharFormat(NULL_FMT) block = block.next()
def copy_lines(self, lo, hi, cursor): """ Copy specified lines from the syntax highlighted buffer into the destination cursor, preserving all formatting created by the syntax highlighter. """ num = hi - lo if num > 0: block = self.findBlockByNumber(lo + 1) while num > 0: num -= 1 cursor.insertText(block.text()) dest_block = cursor.block() c = QTextCursor(dest_block) for af in block.layout().additionalFormats(): start = dest_block.position() + af.start c.setPosition(start), c.setPosition(start + af.length, c.KeepAnchor) c.setCharFormat(af.format) cursor.insertBlock() cursor.setCharFormat(QTextCharFormat()) block = block.next()
def resized(self): ' Resize images to fit in new view size and adjust all line number references accordingly ' for v in (self.left, self.right): changes = [] for i, (top, bot, kind) in enumerate(v.changes): if top in v.images: img, oldw, oldlines = v.images[top] lines, w = self.get_lines_for_image(img, v) if lines != oldlines: changes.append((i, lines, lines - oldlines, img, w)) for i, lines, delta, img, w in changes: top, bot, kind = v.changes[i] c = QTextCursor(v.document().findBlockByNumber(top + 1)) c.beginEditBlock() c.movePosition(c.StartOfBlock) if delta > 0: for _ in xrange(delta): c.insertBlock() else: c.movePosition(c.NextBlock, c.KeepAnchor, -delta) c.removeSelectedText() c.endEditBlock() v.images[top] = (img, w, lines) def mapnum(x): return x if x <= top else x + delta lnm = LineNumberMap() lnm.max_width = v.line_number_map.max_width for x, val in v.line_number_map.iteritems(): dict.__setitem__(lnm, mapnum(x), val) v.line_number_map = lnm v.changes = [(mapnum(t), mapnum(b), k) for t, b, k in v.changes] v.headers = [(mapnum(x), name) for x, name in v.headers] v.images = OrderedDict( (mapnum(x), v) for x, v in v.images.iteritems()) v.viewport().update()
def search(self, query, reverse=False): ''' Search for query, also searching the headers. Matches in headers are not highlighted as managing the highlight is too much of a pain.''' if not query.strip(): return c = self.textCursor() lnum = c.block().blockNumber() cpos = c.positionInBlock() headers = dict(self.headers) if lnum in headers: cpos = self.search_header_pos lines = unicode(self.toPlainText()).splitlines() for hn, text in self.headers: lines[hn] = text prefix, postfix = lines[lnum][:cpos], lines[lnum][cpos:] before, after = enumerate( lines[0:lnum]), ((lnum + 1 + i, x) for i, x in enumerate(lines[lnum + 1:])) if reverse: sl = chain([(lnum, prefix)], reversed(tuple(before)), reversed(tuple(after)), [(lnum, postfix)]) else: sl = chain([(lnum, postfix)], after, before, [(lnum, prefix)]) flags = regex.REVERSE if reverse else 0 pat = regex.compile(regex.escape(query, special_only=True), flags=regex.UNICODE | regex.IGNORECASE | flags) for num, text in sl: try: m = next(pat.finditer(text)) except StopIteration: continue start, end = m.span() length = end - start if text is postfix: start += cpos c = QTextCursor(self.document().findBlockByNumber(num)) c.setPosition(c.position() + start) if num in headers: self.search_header_pos = start + length else: c.setPosition(c.position() + length, c.KeepAnchor) self.search_header_pos = 0 if reverse: pos, anchor = c.position(), c.anchor() c.setPosition(pos), c.setPosition(anchor, c.KeepAnchor) self.setTextCursor(c) self.centerCursor() self.scrolled.emit() break else: info_dialog(self, _('No matches found'), _('No matches found for query: %s' % query), show=True)
def show_context_menu(self, pos): m = QMenu(self) a = m.addAction c = self.editor.cursorForPosition(pos) origc = QTextCursor(c) r = origr = self.editor.syntax_range_for_cursor(c) if (r is None or not r.format.property(SPELL_PROPERTY).toBool() ) and c.positionInBlock() > 0: c.setPosition(c.position() - 1) r = self.editor.syntax_range_for_cursor(c) if r is not None and r.format.property(SPELL_PROPERTY).toBool(): word = self.editor.text_for_range(c.block(), r) locale = self.editor.spellcheck_locale_for_cursor(c) orig_pos = c.position() c.setPosition(orig_pos - utf16_length(word)) found = False self.editor.setTextCursor(c) if self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False): found = True fc = self.editor.textCursor() if fc.position() < c.position(): self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False) if found: suggestions = dictionaries.suggestions(word, locale)[:7] if suggestions: for suggestion in suggestions: ac = m.addAction( suggestion, partial(self.editor.simple_replace, suggestion)) f = ac.font() f.setBold(True), ac.setFont(f) m.addSeparator() m.addAction(actions['spell-next']) m.addAction(_('Ignore this word'), partial(self._nuke_word, None, word, locale)) dics = dictionaries.active_user_dictionaries if len(dics) > 0: if len(dics) == 1: m.addAction( _('Add this word to the dictionary: {0}').format( dics[0].name), partial(self._nuke_word, dics[0].name, word, locale)) else: ac = m.addAction(_('Add this word to the dictionary')) dmenu = QMenu(m) ac.setMenu(dmenu) for dic in dics: dmenu.addAction( dic.name, partial(self._nuke_word, dic.name, word, locale)) m.addSeparator() if origr is not None and origr.format.property(LINK_PROPERTY).toBool(): href = self.editor.text_for_range(origc.block(), origr) m.addAction( _('Open %s') % href, partial(self.link_clicked.emit, href)) if origr is not None and ( origr.format.property(TAG_NAME_PROPERTY).toBool() or origr.format.property(CSS_PROPERTY).toBool()): word = self.editor.text_for_range(origc.block(), origr) item_type = 'tag_name' if origr.format.property( TAG_NAME_PROPERTY).toBool() else 'css_property' url = help_url(word, item_type, self.editor.highlighter.doc_name, extra_data=current_container().opf_version) if url is not None: m.addAction( _('Show help for: %s') % word, partial(open_url, url)) for x in ('undo', 'redo'): a(actions['editor-%s' % x]) m.addSeparator() for x in ('cut', 'copy', 'paste'): a(actions['editor-' + x]) m.addSeparator() m.addAction(_('&Select all'), self.editor.select_all) m.addAction(actions['mark-selected-text']) if self.syntax == 'html': m.addAction(actions['multisplit']) m.exec_(self.editor.mapToGlobal(pos))
def resized(self): ' Resize images to fit in new view size and adjust all line number references accordingly ' for v in (self.left, self.right): changes = [] for i, (top, bot, kind) in enumerate(v.changes): if top in v.images: img, oldw, oldlines = v.images[top] lines, w = self.get_lines_for_image(img, v) if lines != oldlines: changes.append((i, lines, lines - oldlines, img, w)) for i, lines, delta, img, w in changes: top, bot, kind = v.changes[i] c = QTextCursor(v.document().findBlockByNumber(top+1)) c.beginEditBlock() c.movePosition(c.StartOfBlock) if delta > 0: for _ in xrange(delta): c.insertBlock() else: c.movePosition(c.NextBlock, c.KeepAnchor, -delta) c.removeSelectedText() c.endEditBlock() v.images[top] = (img, w, lines) def mapnum(x): return x if x <= top else x + delta lnm = LineNumberMap() lnm.max_width = v.line_number_map.max_width for x, val in v.line_number_map.iteritems(): dict.__setitem__(lnm, mapnum(x), val) v.line_number_map = lnm v.changes = [(mapnum(t), mapnum(b), kind) for t, b, kind in v.changes] v.headers = [(mapnum(x), name) for x, name in v.headers] v.images = OrderedDict((mapnum(x), v) for x, v in v.images.iteritems()) v.viewport().update()
def search(self, query, reverse=False): ''' Search for query, also searching the headers. Matches in headers are not highlighted as managing the highlight is too much of a pain.''' if not query.strip(): return c = self.textCursor() lnum = c.block().blockNumber() cpos = c.positionInBlock() headers = dict(self.headers) if lnum in headers: cpos = self.search_header_pos lines = unicode(self.toPlainText()).splitlines() for hn, text in self.headers: lines[hn] = text prefix, postfix = lines[lnum][:cpos], lines[lnum][cpos:] before, after = enumerate(lines[0:lnum]), ((lnum+1+i, x) for i, x in enumerate(lines[lnum+1:])) if reverse: sl = chain([(lnum, prefix)], reversed(tuple(before)), reversed(tuple(after)), [(lnum, postfix)]) else: sl = chain([(lnum, postfix)], after, before, [(lnum, prefix)]) flags = regex.REVERSE if reverse else 0 pat = regex.compile(regex.escape(query, special_only=True), flags=regex.UNICODE|regex.IGNORECASE|flags) for num, text in sl: try: m = next(pat.finditer(text)) except StopIteration: continue start, end = m.span() length = end - start if text is postfix: start += cpos c = QTextCursor(self.document().findBlockByNumber(num)) c.setPosition(c.position() + start) if num in headers: self.search_header_pos = start + length else: c.setPosition(c.position() + length, c.KeepAnchor) self.search_header_pos = 0 if reverse: pos, anchor = c.position(), c.anchor() c.setPosition(pos), c.setPosition(anchor, c.KeepAnchor) self.setTextCursor(c) self.centerCursor() self.scrolled.emit() break else: info_dialog(self, _('No matches found'), _( 'No matches found for query: %s' % query), show=True)
def show_context_menu(self, pos): m = QMenu(self) a = m.addAction c = self.editor.cursorForPosition(pos) origc = QTextCursor(c) r = origr = self.editor.syntax_range_for_cursor(c) if (r is None or not r.format.property(SPELL_PROPERTY).toBool()) and c.positionInBlock() > 0: c.setPosition(c.position() - 1) r = self.editor.syntax_range_for_cursor(c) if r is not None and r.format.property(SPELL_PROPERTY).toBool(): word = self.editor.text_for_range(c.block(), r) locale = self.editor.spellcheck_locale_for_cursor(c) orig_pos = c.position() c.setPosition(orig_pos - utf16_length(word)) found = False self.editor.setTextCursor(c) if self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False): found = True fc = self.editor.textCursor() if fc.position() < c.position(): self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False) if found: suggestions = dictionaries.suggestions(word, locale)[:7] if suggestions: for suggestion in suggestions: ac = m.addAction(suggestion, partial(self.editor.simple_replace, suggestion)) f = ac.font() f.setBold(True), ac.setFont(f) m.addSeparator() m.addAction(actions['spell-next']) m.addAction(_('Ignore this word'), partial(self._nuke_word, None, word, locale)) dics = dictionaries.active_user_dictionaries if len(dics) > 0: if len(dics) == 1: m.addAction(_('Add this word to the dictionary: {0}').format(dics[0].name), partial( self._nuke_word, dics[0].name, word, locale)) else: ac = m.addAction(_('Add this word to the dictionary')) dmenu = QMenu(m) ac.setMenu(dmenu) for dic in dics: dmenu.addAction(dic.name, partial(self._nuke_word, dic.name, word, locale)) m.addSeparator() if origr is not None and origr.format.property(LINK_PROPERTY).toBool(): href = self.editor.text_for_range(origc.block(), origr) m.addAction(_('Open %s') % href, partial(self.link_clicked.emit, href)) if origr is not None and (origr.format.property(TAG_NAME_PROPERTY).toBool() or origr.format.property(CSS_PROPERTY).toBool()): word = self.editor.text_for_range(origc.block(), origr) item_type = 'tag_name' if origr.format.property(TAG_NAME_PROPERTY).toBool() else 'css_property' url = help_url(word, item_type, self.editor.highlighter.doc_name, extra_data=current_container().opf_version) if url is not None: m.addAction(_('Show help for: %s') % word, partial(open_url, url)) for x in ('undo', 'redo'): a(actions['editor-%s' % x]) m.addSeparator() for x in ('cut', 'copy', 'paste'): a(actions['editor-' + x]) m.addSeparator() m.addAction(_('&Select all'), self.editor.select_all) m.addAction(actions['mark-selected-text']) if self.syntax == 'html': m.addAction(actions['multisplit']) m.exec_(self.editor.mapToGlobal(pos))
class PksMain(QMainWindow, Ui_PksWindow): def __init__(self): super(PksMain, self).__init__() self.setupUi(self) self.ksy_file = None self.ks_parser = None self.targetfile = None self.base_struct = None self._parsed = None self._parse_tree = None self._cursor = None self._ksc_path = "C:/Program Files (x86)/kaitai-struct-compiler/bin/kaitai-struct-compiler.bat" self.data_tree.itemSelectionChanged.connect(self._tree_line_selected) self.reloadButton.clicked.connect(self._load_data) self.ksyFileButton.clicked.connect(self._change_ksy) self.targetFileButton.clicked.connect(self._change_targetfile) self.reloadButton.setEnabled(False) def _set_tree_model(self, parsing, parent=None): self.data_tree.clear() for field_info in parsing: item = QTreeWidgetItem(parent) item.setText(0, "%s" % field_info['name']) item.setData(0, QtCore.Qt.UserRole, (field_info['offset'], field_info['length'])) if isinstance(field_info['value'], list): self._set_tree_model(field_info['value'], item) else: child = QTreeWidgetItem(item) child.setText(0, repr(field_info['value'])) if parent is None: self.data_tree.addTopLevelItem(item) self.data_tree.expandItem(item) def _set_ksy_window(self): ksy_text = open(self.ksy_file).read() self.ksy_editor.setPlainText(ksy_text) def _set_data_window(self): self.file_display.setReadOnly(True) self._data = open(self.targetfile, 'rb').read() text = [] for i in range(0, len(self._data), CHARS_PER_LINE): line = self._data[i:i + CHARS_PER_LINE] text.append(hexdump(line)) self.file_display.setPlainText("\n".join(text)) self._cursor = QTextCursor(self.file_display.document()) self.file_display.setTextCursor(self._cursor) def _clear_data_highlight(self): if self._cursor is not None: unhighlight = QtGui.QTextCharFormat() unhighlight.clearBackground() self._cursor.setCharFormat(unhighlight) self._cursor = None def _tree_line_selected(self): if not self.data_tree.selectedItems(): return item = self.data_tree.selectedItems()[0] data = item.data(0, QtCore.Qt.UserRole) while data is None: item = self.data_tree.itemAbove(item) if item is None: break data = item.data(0, QtCore.Qt.UserRole) if data is not None: start, length = data self._clear_data_highlight() curs_start = to_display_pos(start) curs_end = to_display_pos(start + length) - 1 if self._cursor is None: self._cursor = QTextCursor(self.file_display.document()) self.file_display.setTextCursor(self._cursor) self._cursor.setPosition(curs_start) self._cursor.setPosition(curs_end, mode=self._cursor.KeepAnchor) highlight = QtGui.QTextCharFormat() highlight.setBackground(QtGui.QBrush(QtGui.QColor("red"))) self._cursor.mergeCharFormat(highlight) def _change_ksy(self): fname = dialog = QFileDialog.getOpenFileName(self, 'Select .ksy file', '', 'KSY Files (*.ksy)') if fname is None: return self.ksy_file = fname self.ksy_filename_value_label.setText(fname) self._load_data() def _change_targetfile(self): fname = dialog = QFileDialog.getOpenFileName(self, 'Select target file', '', 'All files (*)') if fname is None: return self.targetfile = fname self.target_file_label.setText(fname) self._load_data() def _load_data(self): self._clear_data_highlight() self.reloadButton.setEnabled(False) if self.ksy_file is None: return self.reloadButton.setEnabled(True) base_dir = os.path.dirname(self.ksy_file) self.ks_parser = get_ks_parser_file_from_ksy_file(self.ksy_file) parser_file_name = base_dir + "/" + self.ks_parser + ".py" self.parser_file_name.setText(parser_file_name) if self.ks_parser is None: self.data_tree.clear() self.base_struct = None self._parsed = None return os.chdir(base_dir) subprocess.call([self._ksc_path, "-t", "python", self.ksy_file], cwd=base_dir) self.base_struct, base_parser_name = pks_inspector.get_base_parser( self.ks_parser, parser_file_name) if base_parser_name is None: self.root_parser_class_label.setText( "ERROR - cannot find Python parser for .ksy file") else: self.root_parser_class_label.setText(base_parser_name) if self.targetfile is not None and os.path.exists( self.targetfile) and base_parser_name is not None: self._parsed = self.base_struct.from_file(self.targetfile) self._parse_tree = pks_inspector.to_tree(self._parsed) self._set_tree_model(self._parse_tree) self._set_ksy_window() self._set_data_window()