如何在 QScintilla 中使用自定义折叠图标?
How to use custom folding icons in QScintilla?
考虑这个片段:
import sys
from PyQt5.Qsci import QsciScintilla
from PyQt5.Qt import *
if __name__ == '__main__':
app = QApplication(sys.argv)
view = QsciScintilla()
# http://www.scintilla.org/ScintillaDoc.html#Folding
view.setFolding(QsciScintilla.BoxedTreeFoldStyle)
# view.setFolding(QsciScintilla.BoxedFoldStyle)
# view.setFolding(QsciScintilla.CircledFoldStyle)
# view.setFolding(QsciScintilla.CircledTreeFoldStyle)
# view.setFolding(QsciScintilla.NoFoldStyle)
# view.setFolding(QsciScintilla.PlainFoldStyle)
lines = [
(0, "def foo():"),
(1, " x = 10"),
(1, " y = 20"),
(1, " return x+y"),
(-1, ""),
(0, "def bar(x):"),
(1, " if x > 0:"),
(2, " print('this is')"),
(2, " print('branch1')"),
(1, " else:"),
(2, " print('and this')"),
(2, " print('is branch2')"),
(-1, ""),
(-1, ""),
(-1, ""),
(-1, "print('end')"),
]
view.setText("\n".join([b for a, b in lines]))
MASK = QsciScintilla.SC_FOLDLEVELNUMBERMASK
for i, tpl in enumerate(lines):
level, line = tpl
if level >= 0:
view.SendScintilla(view.SCI_SETFOLDLEVEL, i, level | QsciScintilla.SC_FOLDLEVELHEADERFLAG)
else:
view.SendScintilla(view.SCI_SETFOLDLEVEL, i, 0)
view.show()
app.exec_()
我想弄清楚是否可以将折叠图标更改为与 QScintilla 提供的图标不同的自定义图标,特别是我想要一个类似于 Sublime 的向下箭头:
通过查看 QSciScintilla foldstyle,您可以看到没有任何相似之处。
其实不仅如此,我还想知道当鼠标放在enters/leaves边距区域时,是否可以实现折叠图标淡出的这种微妙效果,看看:
这是一个非常好的功能,这样您在编码时就不会被 "always visible" 折叠图标分散注意力。
在这个thread中似乎有一个叫做SC_MARK_ARROWDOWN
的东西但是不确定它是否可以用作折叠图标......无论如何,我还是更喜欢我的自定义图片因为我将使用 monokai 主题,我希望图标看起来像 Sublime 的一样优雅。
您会在下面找到我创建的 2 个 12x12 png 来表示深色和浅色箭头。
缺少悬停标记突出显示的初步版本:
from PyQt5.Qsci import QsciScintilla
from PyQt5.Qt import *
def get_arrow(down, size, cmin, cmax):
from PIL import Image
from PIL.ImageQt import ImageQt
from math import atan2, cos, pi
pict = Image.new("RGBA", (size, size), tuple(cmin + [255]))
pixl = pict.load()
step = 3.25**2
for y in range(pict.size[1]):
for x in range(pict.size[0]):
xpos = x - pict.size[0] // 2 + 1
ypos = y - pict.size[1] // 2 + 1
retn = 1.5 * atan2(ypos, xpos) + (pi / 4, 0)[not down]
retn = ((abs(cos(retn))**2.5 + 3) * 2)**2 - xpos**2 - ypos**2
if (retn < 0):
retn = cmin
elif (retn >= step):
retn = cmax
else:
retn = [int((cmax[i] - cmin[i]) * retn / step + cmin[i])
for i in range(0, 3)]
pixl[x, y] = tuple(retn + [255])
return ImageQt(pict)
def color_to_sc(c):
return (c[0] & 0xFF) | ((c[1] & 0xFF) << 8) | ((c[2] & 0xFF) << 16)
def set_fold(prev, line, fold, full):
if (prev[0] >= 0):
fmax = max(fold, prev[1])
for iter in range(prev[0], line + 1):
view.SendScintilla(view.SCI_SETFOLDLEVEL, iter,
fmax | (0, view.SC_FOLDLEVELHEADERFLAG)[iter + 1 < full])
def line_empty(line):
return view.SendScintilla(view.SCI_GETLINEENDPOSITION, line) \
<= view.SendScintilla(view.SCI_GETLINEINDENTPOSITION, line)
def modify(position, modificationType, text, length, linesAdded,
line, foldLevelNow, foldLevelPrev, token, annotationLinesAdded):
full = view.SC_MOD_INSERTTEXT | view.SC_MOD_DELETETEXT
if (~modificationType & full == full):
return
prev = [-1, 0]
full = view.SendScintilla(view.SCI_GETLINECOUNT)
lbgn = view.SendScintilla(view.SCI_LINEFROMPOSITION, position)
lend = view.SendScintilla(view.SCI_LINEFROMPOSITION, position + length)
for iter in range(max(lbgn - 1, 0), -1, -1):
if ((iter == 0) or not line_empty(iter)):
lbgn = iter
break
for iter in range(min(lend + 1, full), full + 1):
if ((iter == full) or not line_empty(iter)):
lend = min(iter + 1, full)
break
for iter in range(lbgn, lend):
if (line_empty(iter)):
if (prev[0] == -1):
prev[0] = iter
else:
fold = view.SendScintilla(view.SCI_GETLINEINDENTATION, iter)
fold //= view.SendScintilla(view.SCI_GETTABWIDTH)
set_fold(prev, iter - 1, fold, full)
set_fold([iter, fold], iter, fold, full)
prev = [-1, fold]
set_fold(prev, lend - 1, 0, full)
def hover(position, xpos, ypos):
mask = view.SendScintilla(view.SCI_GETMARGINMASKN, 2)
mask = (mask | view.SC_MASK_FOLDERS, mask & ~view.SC_MASK_FOLDERS) \
[xpos > StandardMarginWidth]
view.SendScintilla(view.SCI_SETMARGINMASKN, 2, mask)
if __name__ == '__main__':
import sys
import textwrap
app = QApplication(sys.argv)
view = QsciScintilla()
view.SendScintilla(view.SCI_SETMULTIPLESELECTION, True)
view.SendScintilla(view.SCI_SETMULTIPASTE, 1)
view.SendScintilla(view.SCI_SETADDITIONALSELECTIONTYPING, True)
view.SendScintilla(view.SCI_SETINDENTATIONGUIDES, view.SC_IV_REAL);
view.SendScintilla(view.SCI_SETTABWIDTH, 4)
view.setFolding(view.BoxedFoldStyle)
StandardIconSize = 16
StandardMarginWidth = 20
StandardBackground = [64, 64, 64]
StandardForeground = [192, 192, 192] # [R, G, B]
view.SendScintilla(view.SCI_SETMARGINWIDTHN, 1, 0)
view.SendScintilla(view.SCI_SETMARGINWIDTHN, 2, StandardMarginWidth)
view.SendScintilla(view.SCI_STYLESETBACK, view.STYLE_LINENUMBER, color_to_sc(StandardBackground))
view.SendScintilla(view.SCI_SETFOLDMARGINHICOLOUR, True, color_to_sc(StandardBackground))
view.SendScintilla(view.SCI_SETFOLDMARGINCOLOUR, True, color_to_sc(StandardBackground))
view.SendScintilla(view.SCI_RGBAIMAGESETHEIGHT, StandardIconSize)
view.SendScintilla(view.SCI_RGBAIMAGESETWIDTH, StandardIconSize)
fldr = get_arrow(0, StandardIconSize, StandardBackground, StandardForeground)
open = get_arrow(1, StandardIconSize, StandardBackground, StandardForeground)
view.SendScintilla(view.SCI_MARKERDEFINERGBAIMAGE, view.SC_MARKNUM_FOLDER, fldr)
view.SendScintilla(view.SCI_MARKERDEFINERGBAIMAGE, view.SC_MARKNUM_FOLDEROPEN, open)
view.SendScintilla(view.SCI_SETMOUSEDWELLTIME, 50)
view.SCN_DWELLSTART.connect(hover)
view.SCN_DWELLEND.connect(hover)
view.SCN_MODIFIED.connect(modify)
NUM_CHUNKS = 1
chunk = textwrap.dedent("""\
def foo():
x = 10
y = 20
return x+y
def bar(x):
if x > 0:
print('this is')
print('branch1')
else:
print('and this')
print('is branch2')
print('end')
""")
view.setText("\n".join([chunk for i in range(NUM_CHUNKS)]))
view.show()
app.exec_()
考虑这个片段:
import sys
from PyQt5.Qsci import QsciScintilla
from PyQt5.Qt import *
if __name__ == '__main__':
app = QApplication(sys.argv)
view = QsciScintilla()
# http://www.scintilla.org/ScintillaDoc.html#Folding
view.setFolding(QsciScintilla.BoxedTreeFoldStyle)
# view.setFolding(QsciScintilla.BoxedFoldStyle)
# view.setFolding(QsciScintilla.CircledFoldStyle)
# view.setFolding(QsciScintilla.CircledTreeFoldStyle)
# view.setFolding(QsciScintilla.NoFoldStyle)
# view.setFolding(QsciScintilla.PlainFoldStyle)
lines = [
(0, "def foo():"),
(1, " x = 10"),
(1, " y = 20"),
(1, " return x+y"),
(-1, ""),
(0, "def bar(x):"),
(1, " if x > 0:"),
(2, " print('this is')"),
(2, " print('branch1')"),
(1, " else:"),
(2, " print('and this')"),
(2, " print('is branch2')"),
(-1, ""),
(-1, ""),
(-1, ""),
(-1, "print('end')"),
]
view.setText("\n".join([b for a, b in lines]))
MASK = QsciScintilla.SC_FOLDLEVELNUMBERMASK
for i, tpl in enumerate(lines):
level, line = tpl
if level >= 0:
view.SendScintilla(view.SCI_SETFOLDLEVEL, i, level | QsciScintilla.SC_FOLDLEVELHEADERFLAG)
else:
view.SendScintilla(view.SCI_SETFOLDLEVEL, i, 0)
view.show()
app.exec_()
我想弄清楚是否可以将折叠图标更改为与 QScintilla 提供的图标不同的自定义图标,特别是我想要一个类似于 Sublime 的向下箭头:
通过查看 QSciScintilla foldstyle,您可以看到没有任何相似之处。
其实不仅如此,我还想知道当鼠标放在enters/leaves边距区域时,是否可以实现折叠图标淡出的这种微妙效果,看看:
这是一个非常好的功能,这样您在编码时就不会被 "always visible" 折叠图标分散注意力。
在这个thread中似乎有一个叫做SC_MARK_ARROWDOWN
的东西但是不确定它是否可以用作折叠图标......无论如何,我还是更喜欢我的自定义图片因为我将使用 monokai 主题,我希望图标看起来像 Sublime 的一样优雅。
您会在下面找到我创建的 2 个 12x12 png 来表示深色和浅色箭头。
缺少悬停标记突出显示的初步版本:
from PyQt5.Qsci import QsciScintilla
from PyQt5.Qt import *
def get_arrow(down, size, cmin, cmax):
from PIL import Image
from PIL.ImageQt import ImageQt
from math import atan2, cos, pi
pict = Image.new("RGBA", (size, size), tuple(cmin + [255]))
pixl = pict.load()
step = 3.25**2
for y in range(pict.size[1]):
for x in range(pict.size[0]):
xpos = x - pict.size[0] // 2 + 1
ypos = y - pict.size[1] // 2 + 1
retn = 1.5 * atan2(ypos, xpos) + (pi / 4, 0)[not down]
retn = ((abs(cos(retn))**2.5 + 3) * 2)**2 - xpos**2 - ypos**2
if (retn < 0):
retn = cmin
elif (retn >= step):
retn = cmax
else:
retn = [int((cmax[i] - cmin[i]) * retn / step + cmin[i])
for i in range(0, 3)]
pixl[x, y] = tuple(retn + [255])
return ImageQt(pict)
def color_to_sc(c):
return (c[0] & 0xFF) | ((c[1] & 0xFF) << 8) | ((c[2] & 0xFF) << 16)
def set_fold(prev, line, fold, full):
if (prev[0] >= 0):
fmax = max(fold, prev[1])
for iter in range(prev[0], line + 1):
view.SendScintilla(view.SCI_SETFOLDLEVEL, iter,
fmax | (0, view.SC_FOLDLEVELHEADERFLAG)[iter + 1 < full])
def line_empty(line):
return view.SendScintilla(view.SCI_GETLINEENDPOSITION, line) \
<= view.SendScintilla(view.SCI_GETLINEINDENTPOSITION, line)
def modify(position, modificationType, text, length, linesAdded,
line, foldLevelNow, foldLevelPrev, token, annotationLinesAdded):
full = view.SC_MOD_INSERTTEXT | view.SC_MOD_DELETETEXT
if (~modificationType & full == full):
return
prev = [-1, 0]
full = view.SendScintilla(view.SCI_GETLINECOUNT)
lbgn = view.SendScintilla(view.SCI_LINEFROMPOSITION, position)
lend = view.SendScintilla(view.SCI_LINEFROMPOSITION, position + length)
for iter in range(max(lbgn - 1, 0), -1, -1):
if ((iter == 0) or not line_empty(iter)):
lbgn = iter
break
for iter in range(min(lend + 1, full), full + 1):
if ((iter == full) or not line_empty(iter)):
lend = min(iter + 1, full)
break
for iter in range(lbgn, lend):
if (line_empty(iter)):
if (prev[0] == -1):
prev[0] = iter
else:
fold = view.SendScintilla(view.SCI_GETLINEINDENTATION, iter)
fold //= view.SendScintilla(view.SCI_GETTABWIDTH)
set_fold(prev, iter - 1, fold, full)
set_fold([iter, fold], iter, fold, full)
prev = [-1, fold]
set_fold(prev, lend - 1, 0, full)
def hover(position, xpos, ypos):
mask = view.SendScintilla(view.SCI_GETMARGINMASKN, 2)
mask = (mask | view.SC_MASK_FOLDERS, mask & ~view.SC_MASK_FOLDERS) \
[xpos > StandardMarginWidth]
view.SendScintilla(view.SCI_SETMARGINMASKN, 2, mask)
if __name__ == '__main__':
import sys
import textwrap
app = QApplication(sys.argv)
view = QsciScintilla()
view.SendScintilla(view.SCI_SETMULTIPLESELECTION, True)
view.SendScintilla(view.SCI_SETMULTIPASTE, 1)
view.SendScintilla(view.SCI_SETADDITIONALSELECTIONTYPING, True)
view.SendScintilla(view.SCI_SETINDENTATIONGUIDES, view.SC_IV_REAL);
view.SendScintilla(view.SCI_SETTABWIDTH, 4)
view.setFolding(view.BoxedFoldStyle)
StandardIconSize = 16
StandardMarginWidth = 20
StandardBackground = [64, 64, 64]
StandardForeground = [192, 192, 192] # [R, G, B]
view.SendScintilla(view.SCI_SETMARGINWIDTHN, 1, 0)
view.SendScintilla(view.SCI_SETMARGINWIDTHN, 2, StandardMarginWidth)
view.SendScintilla(view.SCI_STYLESETBACK, view.STYLE_LINENUMBER, color_to_sc(StandardBackground))
view.SendScintilla(view.SCI_SETFOLDMARGINHICOLOUR, True, color_to_sc(StandardBackground))
view.SendScintilla(view.SCI_SETFOLDMARGINCOLOUR, True, color_to_sc(StandardBackground))
view.SendScintilla(view.SCI_RGBAIMAGESETHEIGHT, StandardIconSize)
view.SendScintilla(view.SCI_RGBAIMAGESETWIDTH, StandardIconSize)
fldr = get_arrow(0, StandardIconSize, StandardBackground, StandardForeground)
open = get_arrow(1, StandardIconSize, StandardBackground, StandardForeground)
view.SendScintilla(view.SCI_MARKERDEFINERGBAIMAGE, view.SC_MARKNUM_FOLDER, fldr)
view.SendScintilla(view.SCI_MARKERDEFINERGBAIMAGE, view.SC_MARKNUM_FOLDEROPEN, open)
view.SendScintilla(view.SCI_SETMOUSEDWELLTIME, 50)
view.SCN_DWELLSTART.connect(hover)
view.SCN_DWELLEND.connect(hover)
view.SCN_MODIFIED.connect(modify)
NUM_CHUNKS = 1
chunk = textwrap.dedent("""\
def foo():
x = 10
y = 20
return x+y
def bar(x):
if x > 0:
print('this is')
print('branch1')
else:
print('and this')
print('is branch2')
print('end')
""")
view.setText("\n".join([chunk for i in range(NUM_CHUNKS)]))
view.show()
app.exec_()