Skip to content

Commit f37906c

Browse files
committed
testing lntextedit
1 parent 98f4c01 commit f37906c

File tree

2 files changed

+153
-17
lines changed

2 files changed

+153
-17
lines changed

__init__.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@
55
import re
66
import codecs
77
from PySide2.QtWidgets import (QLineEdit, QPushButton, QApplication, QTextEdit, QWidget,
8-
QVBoxLayout, QHBoxLayout, QDialog, QFileSystemModel, QTreeView, QLabel, QSplitter,
9-
QInputDialog, QMessageBox, QHeaderView, QMenu, QAction, QKeySequenceEdit,
10-
QPlainTextEdit)
8+
QVBoxLayout, QHBoxLayout, QDialog, QFileSystemModel, QTreeView, QLabel, QSplitter,
9+
QInputDialog, QMessageBox, QHeaderView, QMenu, QAction, QKeySequenceEdit,
10+
QPlainTextEdit)
1111
from PySide2.QtCore import (QDir, QObject, Qt, QFileInfo, QItemSelectionModel, QSettings, QUrl)
1212
from PySide2.QtGui import (QFont, QFontMetrics, QDesktopServices, QKeySequence, QIcon)
1313
from binaryninja import user_plugin_path
1414
from binaryninja.plugin import PluginCommand, MainThreadActionHandler
1515
from binaryninja.mainthread import execute_on_main_thread
1616
from binaryninja.log import (log_error, log_debug)
17-
from binaryninjaui import (getMonospaceFont, UIAction, UIActionHandler, Menu, DockHandler)
17+
from binaryninjaui import (getMonospaceFont, UIAction, UIActionHandler, Menu, DockHandler,
18+
getThemeColor, ThemeColor)
1819
import numbers
20+
from .lntextedit import LNTextEdit
1921

2022
snippetPath = os.path.realpath(os.path.join(user_plugin_path(), "..", "snippets"))
2123
try:
@@ -33,6 +35,7 @@ def includeWalk(dir, includeExt):
3335
filePaths.append(os.path.join(root, f))
3436
return filePaths
3537

38+
3639
def loadSnippetFromFile(snippetPath):
3740
try:
3841
snippetText = codecs.open(snippetPath, 'r', "utf-8").readlines()
@@ -49,6 +52,7 @@ def loadSnippetFromFile(snippetPath):
4952
''.join(snippetText[2:])
5053
)
5154

55+
5256
def actionFromSnippet(snippetName, snippetDescription):
5357
if not snippetDescription:
5458
shortName = os.path.basename(snippetName)
@@ -113,6 +117,7 @@ def executeSnippet(code, context):
113117
if snippetGlobals['current_address'] != context.address:
114118
context.binaryView.file.navigate(context.binaryView.file.view, snippetGlobals['current_address'])
115119

120+
116121
def makeSnippetFunction(code):
117122
return lambda context: executeSnippet(code, context)
118123

@@ -135,8 +140,9 @@ def __init__(self, context, parent=None):
135140
self.browseButton.setIcon(QIcon.fromTheme("edit-undo"))
136141
self.deleteSnippetButton = QPushButton("Delete")
137142
self.newSnippetButton = QPushButton("New Snippet")
138-
self.edit = QPlainTextEdit()
139-
self.edit.setPlaceholderText("python code")
143+
self.edit = LNTextEdit()
144+
self.edit.edit.setPlaceholderText("python code")
145+
self.edit.edit.highlight_color = getThemeColor(ThemeColor.SelectionColor)
140146
self.resetting = False
141147
self.columns = 3
142148
self.context = context
@@ -151,9 +157,9 @@ def __init__(self, context, parent=None):
151157

152158
#Set Editbox Size
153159
font = getMonospaceFont(self)
154-
self.edit.setFont(font)
160+
self.edit.edit.setFont(font)
155161
font = QFontMetrics(font)
156-
self.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API
162+
self.edit.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API
157163

158164
#Files
159165
self.files = QFileSystemModel()
@@ -240,9 +246,9 @@ def __init__(self, context, parent=None):
240246
if self.tree.selectionModel().hasSelection():
241247
self.selectFile(self.tree.selectionModel().selection(), None)
242248
self.edit.setFocus()
243-
cursor = self.edit.textCursor()
244-
cursor.setPosition(self.edit.document().characterCount()-1)
245-
self.edit.setTextCursor(cursor)
249+
cursor = self.edit.edit.textCursor()
250+
cursor.setPosition(self.edit.edit.document().characterCount()-1)
251+
self.edit.edit.setTextCursor(cursor)
246252
else:
247253
self.readOnly(True)
248254
else:
@@ -276,7 +282,7 @@ def clearSelection(self):
276282
self.currentHotkeyLabel.setText("")
277283
self.currentFileLabel.setText("")
278284
self.snippetDescription.setText("")
279-
self.edit.setPlainText("")
285+
self.edit.edit.setPlainText("")
280286
self.currentFile = ""
281287

282288
def reject(self):
@@ -331,7 +337,7 @@ def loadSnippet(self):
331337
(snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile)
332338
self.snippetDescription.setText(snippetDescription) if snippetDescription else self.snippetDescription.setText("")
333339
self.keySequenceEdit.setKeySequence(snippetKeys) if snippetKeys else self.keySequenceEdit.setKeySequence(QKeySequence(""))
334-
self.edit.setPlainText(snippetCode) if snippetCode else self.edit.setPlainText("")
340+
self.edit.edit.setPlainText(snippetCode) if snippetCode else self.edit.edit.setPlainText("")
335341
self.readOnly(False)
336342

337343
def newFileDialog(self):
@@ -353,7 +359,7 @@ def newFileDialog(self):
353359
def readOnly(self, flag):
354360
self.keySequenceEdit.setEnabled(not flag)
355361
self.snippetDescription.setReadOnly(flag)
356-
self.edit.setReadOnly(flag)
362+
self.edit.edit.setReadOnly(flag)
357363
if flag:
358364
self.snippetDescription.setDisabled(True)
359365
self.edit.setDisabled(True)
@@ -379,15 +385,15 @@ def snippetChanged(self):
379385
return True
380386
if snippetKeys != None and snippetKeys != self.keySequenceEdit.keySequence().toString():
381387
return True
382-
return self.edit.toPlainText() != snippetCode or \
388+
return self.edit.edit.toPlainText() != snippetCode or \
383389
self.snippetDescription.text() != snippetDescription
384390

385391
def save(self):
386392
log_debug("Saving snippet %s" % self.currentFile)
387393
outputSnippet = codecs.open(self.currentFile, "w", "utf-8")
388394
outputSnippet.write("#" + self.snippetDescription.text() + "\n")
389395
outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n")
390-
outputSnippet.write(self.edit.toPlainText())
396+
outputSnippet.write(self.edit.edit.toPlainText())
391397
outputSnippet.close()
392398
self.registerAllSnippets()
393399

@@ -408,17 +414,19 @@ def run(self):
408414
outputSnippet = codecs.open(self.currentFile, "w", "utf-8")
409415
outputSnippet.write("#" + self.snippetDescription.text() + "\n")
410416
outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n")
411-
outputSnippet.write(self.edit.toPlainText())
417+
outputSnippet.write(self.edit.edit.toPlainText())
412418
outputSnippet.close()
413419
self.registerAllSnippets()
414420

415421
def clearHotkey(self):
416422
self.keySequenceEdit.clear()
417423

424+
418425
def launchPlugin(context):
419426
snippets = Snippets(context)
420427
snippets.exec_()
421428

429+
422430
if __name__ == '__main__':
423431
app = QApplication(sys.argv)
424432
snippets = Snippets(None)

lntextedit.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
'''
2+
Text widget with support for line numbers
3+
Originally from: https://nachtimwald.com/2009/08/19/better-qplaintextedit-with-line-numbers/
4+
And available under the MIT license: https://nachtimwald.com/legal/
5+
Adapted to PySide2 by Vector 35
6+
'''
7+
from PySide2.QtWidgets import (QFrame, QHBoxLayout, QPlainTextEdit, QTextEdit, QWidget)
8+
from PySide2.QtGui import (QPainter, QTextFormat)
9+
from PySide2.QtCore import (QRect, Qt)
10+
from builtins import str as unicode
11+
12+
class LNTextEdit(QFrame):
13+
14+
class NumberBar(QWidget):
15+
16+
def __init__(self, edit):
17+
QWidget.__init__(self, edit)
18+
19+
self.edit = edit
20+
self.adjustWidth(1)
21+
22+
def paintEvent(self, event):
23+
self.edit.numberbarPaint(self, event)
24+
QWidget.paintEvent(self, event)
25+
26+
def adjustWidth(self, count):
27+
width = self.fontMetrics().width(unicode(count))
28+
if self.width() != width:
29+
self.setFixedWidth(width)
30+
31+
def updateContents(self, rect, scroll):
32+
if scroll:
33+
self.scroll(0, scroll)
34+
else:
35+
# It would be nice to do
36+
# self.update(0, rect.y(), self.width(), rect.height())
37+
# But we can't because it will not remove the bold on the
38+
# current line if word wrap is enabled and a new block is
39+
# selected.
40+
self.update()
41+
42+
43+
class PlainTextEdit(QPlainTextEdit):
44+
45+
def __init__(self, *args):
46+
QPlainTextEdit.__init__(self, *args)
47+
self.highlight_color = self.palette().alternateBase()
48+
49+
#self.setFrameStyle(QFrame.NoFrame)
50+
51+
self.setFrameStyle(QFrame.NoFrame)
52+
self.highlight()
53+
#self.setLineWrapMode(QPlainTextEdit.NoWrap)
54+
55+
self.cursorPositionChanged.connect(self.highlight)
56+
57+
def highlight(self):
58+
hi_selection = QTextEdit.ExtraSelection()
59+
60+
hi_selection.format.setBackground(self.highlight_color)
61+
hi_selection.format.setProperty(QTextFormat.FullWidthSelection, True)
62+
hi_selection.cursor = self.textCursor()
63+
hi_selection.cursor.clearSelection()
64+
65+
self.setExtraSelections([hi_selection])
66+
67+
def numberbarPaint(self, number_bar, event):
68+
font_metrics = self.fontMetrics()
69+
current_line = self.document().findBlock(self.textCursor().position()).blockNumber() + 1
70+
71+
block = self.firstVisibleBlock()
72+
line_count = block.blockNumber()
73+
painter = QPainter(number_bar)
74+
painter.fillRect(event.rect(), self.palette().base())
75+
76+
# Iterate over all visible text blocks in the document.
77+
while block.isValid():
78+
line_count += 1
79+
block_top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top()
80+
81+
# Check if the position of the block is out side of the visible
82+
# area.
83+
if not block.isVisible() or block_top >= event.rect().bottom():
84+
break
85+
86+
# We want the line number for the selected line to be bold.
87+
if line_count == current_line:
88+
font = painter.font()
89+
font.setBold(True)
90+
painter.setFont(font)
91+
else:
92+
font = painter.font()
93+
font.setBold(False)
94+
painter.setFont(font)
95+
96+
# Draw the line number right justified at the position of the line.
97+
paint_rect = QRect(0, block_top, number_bar.width(), font_metrics.height())
98+
painter.drawText(paint_rect, Qt.AlignRight, unicode(line_count))
99+
100+
block = block.next()
101+
102+
painter.end()
103+
104+
def __init__(self, *args):
105+
QFrame.__init__(self, *args)
106+
107+
self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
108+
109+
self.edit = self.PlainTextEdit()
110+
self.number_bar = self.NumberBar(self.edit)
111+
112+
hbox = QHBoxLayout(self)
113+
hbox.setSpacing(0)
114+
hbox.setMargin(0)
115+
hbox.addWidget(self.number_bar)
116+
hbox.addWidget(self.edit)
117+
118+
self.edit.blockCountChanged.connect(self.number_bar.adjustWidth)
119+
self.edit.updateRequest.connect(self.number_bar.updateContents)
120+
121+
def isModified(self):
122+
return self.edit.document().isModified()
123+
124+
def setModified(self, modified):
125+
self.edit.document().setModified(modified)
126+
127+
def setLineWrapMode(self, mode):
128+
self.edit.setLineWrapMode(mode)

0 commit comments

Comments
 (0)