1

I'm attempting to create a UI in Maya (Python 2.7.11 and Maya Qt 5.6.1. and PySide2) where the OK button is grayed out until the user scrolls to the bottom. I can do this easily with QTextEdit by grabbing the value and maximum from the verticalScrollBar but I can't seem to find similar objects in QWebEngineView or QWebEnginePage. Any Ideas?

obfuscated
  • 53
  • 1
  • 7
  • Oh I thought I left a comment on friday.. I was able to run the code but got something like a unicode error that broke the scroll detect function. The other issue is that I don't really want to to use jinja2 and since the page being loaded is a local file I could just embed the javascript in the page instead of adding it on the fly to page when it gets loaded. – obfuscated Mar 25 '19 at 03:21
  • This is the error: `code ('Finished loading: ', True) Traceback (most recent call last): File "scrool.py", line 69, in onLoadFinished self.load_qwebchannel() File "scrool.py", line 77, in load_qwebchannel self.runJavaScript(content.data().decode()) UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 152: ordinal not in range(128)` – obfuscated Mar 25 '19 at 18:59
  • Maya uses Python 2 – obfuscated Mar 25 '19 at 19:01
  • It has the error with the url you provided – obfuscated Mar 25 '19 at 19:02
  • I'll be stuck with 2.7 until Maya puts out a version that uses 3 but I also didn't expect this particular task to be as tricky as it's proving to be. – obfuscated Mar 25 '19 at 19:25
  • I removed .decode altogether (still got the same error with .decode('ascii')) and it worked and the button is disabled when I scroll to the bottom. So it worked! – obfuscated Mar 25 '19 at 21:37
  • Will do.. I'm trying to get it to work in maya which it sorta is although the button changes condition almost as soon as I start scrolling so I'll have to dig into that but I should beable to figure it out. thanks! – obfuscated Mar 26 '19 at 19:10
  • For some reason update_maximum(scrollMaxY) is sending a value of 0 because that is the result of document.documentElement.scrollHeight - document.documentElement.clientHeight.. if I use one of these values then a spits out a reasonable value but the scroll value never reaches it – obfuscated Mar 26 '19 at 19:27
  • Ah here's the answer: var scrollMaxY = Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight) - window.innerHeight https://stackoverflow.com/questions/17688595/finding-the-maximum-scroll-position-of-a-page – obfuscated Mar 26 '19 at 19:44

1 Answers1

1

In the case you want to get some information from the DOM you have to do it with javascript, in this case we will create an object that will have the required information and export it to the HTML so that each time the scroll status changes it will be updated.

from PySide2 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
from jinja2 import Template

class Element(QtCore.QObject):
    def __init__(self, name, parent=None):
        super(Element, self).__init__(parent)
        self._name = name

    @property
    def name(self):
        return self._name

    def script(self):
        return ''

class YScrollBarListener(Element):
    valueChanged = QtCore.Signal(int)
    maximumChanged = QtCore.Signal(int)

    def __init__(self, name, parent=None):
        super(YScrollBarListener, self).__init__(name, parent)
        self._value = -1
        self._maximum = -1

    @QtCore.Property(int, notify=valueChanged)
    def value(self):
        return self._value

    @QtCore.Property(int, notify=maximumChanged)
    def maximum(self):
        return self._maximum

    def script(self):
        _script = '''
        window.addEventListener("scroll", function(event){
            {{name}}.update_value(this.scrollY);
            // https://stackoverflow.com/a/43571388/6622587
            var scrollMaxY = window.scrollMaxY || (document.documentElement.scrollHeight - document.documentElement.clientHeight)
            {{name}}.update_maximum(scrollMaxY)
        });
        '''
        return Template(_script).render(name=self.name)

    @QtCore.Slot(int)
    def update_value(self, value):
        if self._value != value:
            self._value = value
            self.valueChanged.emit(value)

    @QtCore.Slot(int)
    def update_maximum(self, maximum):
        if self._maximum != maximum:
            self._maximum = maximum
            self.maximumChanged.emit(maximum)

class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
    def __init__(self, *args, **kwargs):
        super(WebEnginePage, self).__init__(*args, **kwargs)
        self.loadFinished.connect(self.onLoadFinished)
        self._objects = []

    def add_object(self, obj):
        self._objects.append(obj)

    @QtCore.Slot(bool)
    def onLoadFinished(self, ok):
        print("Finished loading: ", ok)
        if ok:
            self.load_qwebchannel()
            self.add_objects()

    def load_qwebchannel(self):
        file = QtCore.QFile(":/qtwebchannel/qwebchannel.js")
        if file.open(QtCore.QIODevice.ReadOnly):
            content = file.readAll()
            file.close()
            self.runJavaScript(content.data().decode())
        if self.webChannel() is None:
            channel = QtWebChannel.QWebChannel(self)
            self.setWebChannel(channel)

    def add_objects(self):
        if self.webChannel() is not None:
            objects = {obj.name : obj for obj in self._objects}
            self.webChannel().registerObjects(objects)
            _script = '''
            {% for obj in objects %}
            var {{obj}};
            {% endfor %}
            new QWebChannel(qt.webChannelTransport, function (channel) {
            {% for obj in objects %}
                {{obj}} = channel.objects.{{obj}};
            {% endfor %}
            }); 
            '''
            self.runJavaScript(Template(_script).render(objects=objects.keys()))
            for obj in self._objects:
                if isinstance(obj, Element):
                    self.runJavaScript(obj.script())

class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)

        self._scrollbar_listener = YScrollBarListener("y_scrollbar_listener", self)
        self._scrollbar_listener.valueChanged.connect(self.on_y_value_changed)
        view = QtWebEngineWidgets.QWebEngineView()
        page = WebEnginePage(view)
        page.add_object(self._scrollbar_listener)
        view.setPage(page)

        view.load(QtCore.QUrl("https://stackoverflow.com/questions/43282899"))

        self._button = QtWidgets.QPushButton("button")
        progressbar = QtWidgets.QProgressBar(maximum=100)
        view.loadProgress.connect(progressbar.setValue)

        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(view)
        lay.addWidget(self._button)
        lay.addWidget(progressbar)

        self.resize(640, 480)

    def on_y_value_changed(self, value):
        self._button.setEnabled(self._scrollbar_listener.maximum != value)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    app.setStyle('fusion')
    app.setStyleSheet('''
    QPushButton
    {
        background-color: #2E8B57;
    }
    QPushButton:disabled
    {
        background-color: #FFFAFA;
    }
    ''')
    w = Widget()
    w.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241