【问题标题】:How to replace properly certain matches on QScintilla widget?如何正确替换 QScintilla 小部件上的某些匹配项?
【发布时间】:2017-02-02 21:49:33
【问题描述】:

我得到了这个小 mcve 代码:

import sys
import re

from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtCore import Qt
from PyQt5.Qsci import QsciScintilla
from PyQt5 import Qsci


class FloatSlider(QtWidgets.QWidget):

    value_changed = QtCore.pyqtSignal(float)

    def __init__(self, value=0.0, parent=None):
        super().__init__(parent)

        self.slider = QtWidgets.QSlider(Qt.Horizontal)
        self.label = QtWidgets.QLabel()
        self.label.setAlignment(Qt.AlignCenter)

        self.adjust(value)

        layout = QtWidgets.QHBoxLayout()
        layout.addWidget(self.slider)
        layout.addWidget(self.label)

        self.slider.valueChanged.connect(self.on_value_changed)
        self.setLayout(layout)
        self.setWindowTitle("Adjust number")

    def adjust(self, value):
        width = 100  # TODO: Adjust it properly depending on input value
        self.slider.setRange(value - width, value + width)
        self.slider.setSingleStep(1)
        self.slider.setValue(value)
        self.label.setText(str(float(value)))

    def on_value_changed(self, value):
        # vmax, vmin = self.slider.minimum(), self.slider.maximum()
        # value = 2 * value / (vmax - vmin)
        self.label.setText(str(float(value)))
        self.value_changed.emit(value)


class SimpleEditor(QsciScintilla):

    def __init__(self, language=None, parent=None):
        super().__init__(parent)

        self.slider = FloatSlider(value=0.0)
        self.slider.value_changed.connect(self.float_value_changed)

        font = QtGui.QFont()
        font.setFamily('Courier')
        font.setFixedPitch(True)
        font.setPointSize(10)
        self.setFont(font)
        self.setMarginsFont(font)
        fontmetrics = QtGui.QFontMetrics(font)
        self.setMarginsFont(font)
        self.setMarginWidth(0, fontmetrics.width("00000") + 6)
        self.setMarginLineNumbers(0, True)
        self.setMarginsBackgroundColor(QtGui.QColor("#cccccc"))
        self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
        self.setCaretLineVisible(True)
        self.setCaretLineBackgroundColor(QtGui.QColor("#E8E8FF"))

        if language:
            self.lexer = getattr(Qsci, 'QsciLexer' + language)()
            self.setLexer(self.lexer)

        self.SendScintilla(QsciScintilla.SCI_FOLDALL, True)
        self.setAutoCompletionThreshold(1)
        self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
        self.setFolding(QsciScintilla.BoxedTreeFoldStyle)

        # Signals/Slots
        self.cursorPositionChanged.connect(self.on_cursor_position_changed)
        self.copyAvailable.connect(self.on_copy_available)
        self.indicatorClicked.connect(self.on_indicator_clicked)
        self.indicatorReleased.connect(self.on_indicator_released)
        self.linesChanged.connect(self.on_lines_changed)
        self.marginClicked.connect(self.on_margin_clicked)
        self.modificationAttempted.connect(self.on_modification_attempted)
        self.modificationChanged.connect(self.on_modification_changed)
        self.selectionChanged.connect(self.on_selection_changed)
        self.textChanged.connect(self.on_text_changed)
        self.userListActivated.connect(self.on_user_list_activated)

    def float_value_changed(self, v):
        print(v)

    def on_cursor_position_changed(self, line, index):
        text = self.text(line)
        for match in re.finditer('(?:^|(?<=\W))\d+(?:\.\d+)?(?=$|\W)', text):
            start, end = match.span()
            if start <= index <= end:
                pos = self.positionFromLineIndex(line, start)
                x = self.SendScintilla(
                    QsciScintilla.SCI_POINTXFROMPOSITION, 0, pos)
                y = self.SendScintilla(
                    QsciScintilla.SCI_POINTYFROMPOSITION, 0, pos)
                point = self.mapToGlobal(QtCore.QPoint(x, y))
                num = float(match.group())
                message = 'number: %s' % num

                self.slider.setWindowTitle('line: {0}'.format(line))
                self.slider.adjust(num)
                self.slider.move(point + QtCore.QPoint(0, 20))
                self.slider.show()

                break

    def on_copy_available(self, yes):
        print('-' * 80)
        print("on_copy_available")

    def on_indicator_clicked(self, line, index, state):
        print("on_indicator_clicked")

    def on_indicator_released(self, line, index, state):
        print("on_indicator_released")

    def on_lines_changed(self):
        print("on_lines_changed")

    def on_margin_clicked(self, margin, line, state):
        print("on_margin_clicked")

    def on_modification_attempted(self):
        print("on_modification_attempted")

    def on_modification_changed(self):
        print("on_modification_changed")

    def on_selection_changed(self):
        print("on_selection_changed")

    def on_text_changed(self):
        print("on_text_changed")

    def on_user_list_activated(self, id, text):
        print("on_user_list_activated")


def show_requirements():
    print(sys.version)
    print(QtCore.QT_VERSION_STR)
    print(QtCore.PYQT_VERSION_STR)

if __name__ == "__main__":
    show_requirements()

    app = QtWidgets.QApplication(sys.argv)

    ex = QtWidgets.QWidget()
    hlayout = QtWidgets.QHBoxLayout()
    ed = SimpleEditor("JavaScript")

    hlayout.addWidget(ed)

    ed.setText("""#ifdef GL_ES
precision mediump float;
#endif

#extension GL_OES_standard_derivatives : enable

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;

void main( void ) {

    vec2 st = ( gl_FragCoord.xy / resolution.xy );
    vec2 lefbot = step(vec2(0.1), st);
    float pct = lefbot.x*lefbot.y;
    vec2 rigtop = step(vec2(0.1), 1.-st);
    pct *= rigtop.x*rigtop.y;
    vec3 color = vec3(pct);

    gl_FragColor = vec4( color, 1.0 );""")

    ex.setLayout(hlayout)
    ex.show()
    ex.resize(800, 600)

    sys.exit(app.exec_())

有几个问题我不知道如何解决:

  • 每次更改值时,我的滑块小部件都会更改小部件宽度,我尝试了addStrecht(1),但它没有按我的预期工作,因为小部件之间有太多空白空间(即:布局排列 -> 滑块|拉伸|标签)
  • 一旦我在 QScintilla 小部件上键入数值,FloatSlider 小部件就会出现,这绝对是我不想要的。我希望它仅在我用鼠标左键或任何其他组合(即:ctrl+left_mouse)按下此类数值时出现
  • 我不知道如何实时正确替换 QScintilla 文本(正则表达式匹配)。理想情况下,应该只修改与 QScintilla 匹配的文本,例如,我不想替换整个文本,因为视觉效果会非常令人毛骨悚然

感觉为这些小疑问打开 3 个不同的问题是没问题的,所以我决定将它们收集在同一个线程中。希望没问题

【问题讨论】:

  • 您将这些称为“小问题”的事实让我怀疑您严重低估了这项任务的复杂性。在第二个要点上 - 您绝对确实希望滑块保持可见。如果您尝试使用khan academy example,您会注意到该小工具在您键入时仍然可见。这就是所有自动完成程序、呼叫提示等的工作方式。我不想让你气馁,但要让这个工作正常进行需要很多的努力——尤其是键盘处理。
  • @ekhumoro 我绝对没有低估这个很酷的小部件的创造:)。它涉及几件事:1)创建一个好的 glsl 解析器,我在这个one 和这个one 的工作中遇到了一些麻烦 2)掌握 qscintilla 小部件的可能性(如你所见,我离掌握还很远它) 3) 创建适当的小部件来处理 1/2/3d glsl 类型,我将创建与提供的 here 类似的 pyqt 小部件。无论如何,每次都会有一个小问题:)

标签: python python-3.x pyqt qscintilla


【解决方案1】:

关于第二个要点:我认为您应该放弃为滑块设置单独的窗口/对话框的想法。它应该是一个保持在编辑器顶部的弹出窗口,直到您在编辑器外部单击或按退出键(即像工具提示或上下文菜单)。

为了让您了解它的外观(但不尝试解决任何其他潜在问题),请尝试以下操作:

class FloatSlider(QtWidgets.QFrame):    
    value_changed = QtCore.pyqtSignal(float)

    def __init__(self, value=0.0):
        super().__init__()
        ...    
        self.setFrameShape(QtWidgets.QFrame.Box)
        self.setFrameShadow(QtWidgets.QFrame.Plain)
        self.setParent(None, QtCore.Qt.Popup)
        self.setFocusPolicy(QtCore.Qt.NoFocus)

    def adjust(self, value):
        ...
        self.slider.setFocus()

【讨论】:

    【解决方案2】:
    • 使用setFixedWidth 避免滑块或标签在每次更改时更改宽度
    • 你不应该使用on_cursor_position_changed事件,而应该使用mouseReleaseEvent
    • 使用insertAtSCI_DELETERANGE 方法替换特定位置的特定匹配项的好方法是使用setText。欲了解更多信息,请查看QScintilla docs

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-15
      相关资源
      最近更新 更多