File: evented.py

package info (click to toggle)
python-pyqtlet2 0.9.3-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,672 kB
  • sloc: python: 997; javascript: 88; makefile: 18; sh: 14
file content (180 lines) | stat: -rw-r--r-- 7,669 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import logging

from ... import mapwidget

from qtpy.QtCore import QObject, QJsonValue


class Evented(QObject):
    '''
    Base class for all pyqtlet2 objects.
    Handles initiation, as well as all python<->js communication
    '''
    mapWidgets = []

    def __init__(self, mapWidget=None, mapWidgetIndex=None):
        '''
        Base class for all pyqtlet2 objects
        Handles initiation, as well as python-Js communication
        The first pyqtlet2 object to be initiated should be pyqtlet2.L.map
        This will allow all the pyqtlet2 objects to have access to the
        widget and thus the ability to implement leaflet via python.

        :param pyqtlet2.MapWidget mapWidget: The mapwidget object
            Should only be sent once, when the first object is being 
            initialised.
        '''
        super().__init__()
        self._logger = logging.getLogger(__name__)
        self.response = None

        if isinstance(mapWidgetIndex, type(None)):
            return

        if mapWidget is None:
            raise RuntimeError('L.map must be initialised before other pyqtlet2 objects')
        if not issubclass(type(mapWidget), mapwidget.MapWidget):
            raise TypeError(('Expected mapWidget of type pyqtlet2.MapWidget, '
                            'received {type_}'.format(type_=type(mapWidget))))
        self.mapWidgets.append(mapWidget)
        js = ('var channelObjects = null;'
              'new QWebChannel(qt.webChannelTransport, function(channel) {'
              '    channelObjects = channel.objects;'
              '});')
        self.runJavaScript(js, mapWidgetIndex)
        if mapWidget := self.getMapWidgetAtIndex(mapWidgetIndex):
            mapWidget.page.titleChanged.connect(lambda: print('title changed'))

    def getMapWidgetAtIndex(self, mapWidgetIndex):
        if len(self.mapWidgets) > mapWidgetIndex:
            return self.mapWidgets[mapWidgetIndex]
        self._logger.error("No")
        return None

    def getJsResponse(self, js, mapWidgetIndex, callback):
        '''
        Runs javascript code in the mapWidget and triggers callback.

        Can be used for custom use cases where information is required
        from the mapwidget, and the existing code does not cover the
        requirement
        
        :param str js: The javascript code
        :param function callback: The function that will consume the 
            javascript response

        .. note:: 
            Qt runs runJavaScript function asynchronously. So if we want 
            to get a response from leaflet, we need to force it to be sync
            In all that I have tried, I was unable to get the response from
            the same function, so I am converting it to a method with callback
        '''
        self._logger.debug('Running JS with callback: {js}=>{callback}'.format(
            js=js, callback=callback.__name__))
        if mapWidget := self.getMapWidgetAtIndex(mapWidgetIndex):
            mapWidget.page.runJavaScript(js, callback)
        else:
            self._logger.error(f"Can't find mapWidget at index: {mapWidgetIndex}")

    def runJavaScript(self, js, mapWidgetIndex: int):
        '''
        Runs javascript code in the mapWidget.

        Can be used for custom use cases where the existing code,
        methods etc. do not cover the requirements.

        :param str js: The javascript code
        '''
        self._logger.debug('Running JS: {js}'.format(js=js))
        if mapWidget := self.getMapWidgetAtIndex(mapWidgetIndex):
            mapWidget.page.runJavaScript(js)
        else:
            self._logger.error(f"Can't find mapWidget at index: {mapWidgetIndex}")

    def _createJsObject(self, leafletJsObject, mapWidgetIndex):
        '''
        Function to create variables/objects in leaflet in the
        javascript "engine", and registers the object so that it can
        be called in the webchannel.

        :param str leafletJsObject: javascript code that creates the
            leaflet object
        '''
        # Creates the js object on the mapWidget page
        js = 'var {name} = {jsObject}'.format(name=self.jsName, 
                jsObject=leafletJsObject)
        self.runJavaScript(js, mapWidgetIndex)
        # register the object in the channel
        if mapWidget := self.getMapWidgetAtIndex(mapWidgetIndex):
            mapWidget.channel.registerObject(
                    '{name}Object'.format(name=self.jsName), self)
        else:
            self._logger.error(f"Can't find mapWidget at index: {mapWidgetIndex}")

    def _connectEventToSignal(self, event, signalEmitter, mapWidgetIndex):
        # We need to delete some keys as they are causing circular structures
        js = '{name}.on("{event}", function(e) {{\
                  delete e.target;\
                  delete e.sourceTarget;\
                  e = copyWithoutCircularReferences([e], e);\
                  channelObjects.{name}Object.{signalEmitter}(e)}})'.format(
            name=self.jsName, event=event, signalEmitter=signalEmitter)
        self.runJavaScript(js, mapWidgetIndex)

    def _stringifyForJs(self, object_):
        # When passing options to JS, sometimes we need to pass in objects
        # this method and _handleObject take care of that
        # Some arguments are strings and some are objects. We also make sure
        # that the objects are not sent as strings. Similarly, we also convert
        # python bool to js bool etc.
        jsString = str(self._handleObject(object_))
        jsString = jsString.replace('\'__pyqtletObjectStart__', '')
        jsString = jsString.replace('\"__pyqtletObjectStart__', '')
        jsString = jsString.replace('__pyqtletObjectEnd__\'', '')
        jsString = jsString.replace('__pyqtletObjectEnd__\"', '')
        return jsString

    def _handleObject(self, object_):
        if type(object_) is list:
            return [self._handleObject(item) for item in object_]
        if type(object_) is dict:
            return {key: self._handleObject(object_[key]) for key in object_}
        if issubclass(object_.__class__, Evented):
            return '__pyqtletObjectStart__{name}__pyqtletObjectEnd__'.format(name=object_.jsName)
        if object_ is True:
            return '__pyqtletObjectStart__true__pyqtletObjectEnd__'
        if object_ is False:
            return '__pyqtletObjectStart__false__pyqtletObjectEnd__'
        if object_ is None:
            return '__pyqtletObjectStart__null__pyqtletObjectEnd__'
        return object_

    def _qJsonValueToDict(self, object_):
        # Qt returns QJsonValue from within the QChannel. Converting
        # that into a dict is a small recursive function.
        if type(object_) is QJsonValue:
            return self._qJsonValueToDict(self._qJsonToRespectiveType(object_))
        if type(object_) is list:
            return [self._qJsonValueToDict(item) for item in object_]
        if type(object_) is dict:
            return {key: self._qJsonValueToDict(object_[key]) for key in object_}
        return object_

    def _qJsonToRespectiveType(self, object_):
        # A QJsonValue can be one of many types. This function just
        # converts into the correct type
        if object_.isArray():
            return object_.toArray()
        if object_.isBool():
            return object_.toBool()
        if object_.isDouble():
            return object_.toDouble()
        if object_.isNull():
            return None
        if object_.isObject():
            return object_.toObject()
        if object_.isString():
            return object_.toString()
        if object_.isUndefined():
            return None