TreeWidget.update_browse_name_current_item()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
c 1
b 0
f 1
dl 0
loc 5
rs 9.4285
1
from PyQt5.QtCore import pyqtSignal, QMimeData, QObject, Qt, QSettings
2
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QIcon
3
from PyQt5.QtWidgets import QApplication, QAbstractItemView, QAction
4
5
from opcua import ua
6
from opcua import Node
7
from opcua.ua import UaError
8
9
10
class TreeWidget(QObject):
11
12
    error = pyqtSignal(Exception)
13
14
    def __init__(self, view):
15
        QObject.__init__(self, view)
16
        self.view = view
17
        self.model = TreeViewModel()
18
        self.model.clear()  # FIXME: do we need this?
19
        self.model.error.connect(self.error)
20
        self.view.setModel(self.model)
21
22
        #self.view.setUniformRowHeights(True)
23
        self.model.setHorizontalHeaderLabels(['DisplayName', "BrowseName", 'NodeId'])
24
        self.view.header().setSectionResizeMode(0)
25
        self.view.header().setStretchLastSection(True)
26
        self.view.setSelectionBehavior(QAbstractItemView.SelectRows)
27
        self.settings = QSettings()
28
        state = self.settings.value("tree_widget_state", None)
29
        if state is not None:
30
            self.view.header().restoreState(state)
31
32
        self.actionReload = QAction("Reload", self)
33
        self.actionReload.triggered.connect(self.reload_current)
34
35
    def save_state(self):
36
        self.settings.setValue("tree_widget_state", self.view.header().saveState())
37
38
    def clear(self):
39
        self.model.clear()
40
41
    def set_root_node(self, node):
42
        self.model.clear()
43
        self.model.set_root_node(node)
44
        self.view.expandToDepth(0)
45
46
    def copy_path(self):
47
        path = self.get_current_path()
48
        path_str = ",".join(path)
49
        QApplication.clipboard().setText(path_str)
50
51
    def expand_current_node(self, expand=True):
52
        idx = self.view.currentIndex()
53
        self.view.setExpanded(idx, expand)
54
55
    def expand_to_node(self, node):
56
        """
57
        Expand tree until given node and select it
58
        """
59
        if isinstance(node, str):
60
            idxlist = self.model.match(self.model.index(0, 0), Qt.DisplayRole, node, 1, Qt.MatchExactly|Qt.MatchRecursive)
61
            node = self.model.data(idxlist[0], Qt.UserRole)
62
        path = node.get_path()
63
        for node in path:
64
            # FIXME: this would be the correct way if it would work
65
            #idxlist = self.model.match(self.model.index(0, 0), Qt.UserRole, QVariantnode, 2, Qt.MatchExactly|Qt.MatchRecursive)
66
            try:
67
                text = node.get_display_name().Text
68
            except UaError as ex:
69
                return
70
            idxlist = self.model.match(self.model.index(0, 0), Qt.DisplayRole, text, 1, Qt.MatchExactly|Qt.MatchRecursive)
71
            if idxlist:
72
                idx = idxlist[0]
73
                self.view.setExpanded(idx, True)
74
                self.view.setCurrentIndex(idx)
75
                self.view.activated.emit(idx)
76
77
    def copy_nodeid(self):
78
        node = self.get_current_node()
79
        text = node.nodeid.to_string()
80
        QApplication.clipboard().setText(text)
81
82
    def get_current_path(self):
83
        idx = self.view.currentIndex()
84
        idx = idx.sibling(idx.row(), 0)
85
        it = self.model.itemFromIndex(idx)
86
        path = []
87
        while it and it.data(Qt.UserRole):
88
            node = it.data(Qt.UserRole)
89
            name = node.get_browse_name().to_string()
90
            path.insert(0, name)
91
            it = it.parent()
92
        return path
93
94
    def update_browse_name_current_item(self, bname):
95
        idx = self.view.currentIndex()
96
        idx = idx.sibling(idx.row(), 1)
97
        it = self.model.itemFromIndex(idx)
98
        it.setText(bname.to_string())
99
100
    def update_display_name_current_item(self, dname):
101
        idx = self.view.currentIndex()
102
        idx = idx.sibling(idx.row(), 0)
103
        it = self.model.itemFromIndex(idx)
104
        it.setText(dname.Text)
105
106
    def reload_current(self):
107
        idx = self.view.currentIndex()
108
        idx = idx.sibling(idx.row(), 0)
109
        it = self.model.itemFromIndex(idx)
110
        if not it:
111
            return None
112
        self.reload(it)
113
114
    def reload(self, item=None):
115
        if item is None:
116
            item = self.model.item(0, 0)
117
        for _ in range(item.rowCount()):
118
            child_it = item.child(0, 0)
119
            node = child_it.data(Qt.UserRole)
120
            if node:
121
                self.model.reset_cache(node)
122
            item.takeRow(0)
123
        node = item.data(Qt.UserRole)
124
        if node:
125
            self.model.reset_cache(node)
126
            idx = self.model.indexFromItem(item)
127
            #if self.view.isExpanded(idx):
128
            #self.view.setExpanded(idx, True)
129
130
    def remove_current_item(self):
131
        idx = self.view.currentIndex()
132
        self.model.removeRow(idx.row(), idx.parent())
133
134
    def get_current_node(self, idx=None):
135
        if idx is None:
136
            idx = self.view.currentIndex()
137
        idx = idx.sibling(idx.row(), 0)
138
        it = self.model.itemFromIndex(idx)
139
        if not it:
140
            return None
141
        node = it.data(Qt.UserRole)
142
        if not node:
143
            ex = RuntimeError("Item does not contain node data, report!")
144
            self.error.emit(ex)
145
            raise ex
146
        return node
147
148
149
class TreeViewModel(QStandardItemModel):
150
151
    error = pyqtSignal(Exception)
152
153
    def __init__(self):
154
        super(TreeViewModel, self).__init__()
155
        self._fetched = []
156
157
    def clear(self):
158
        # remove all rows but not header!!
159
        self.removeRows(0, self.rowCount())
160
        self._fetched = []
161
162
    def set_root_node(self, node):
163
        desc = self._get_node_desc(node)
164
        self.add_item(desc, node=node)
165
166
    def _get_node_desc(self, node):
167
        attrs = node.get_attributes([ua.AttributeIds.DisplayName, ua.AttributeIds.BrowseName, ua.AttributeIds.NodeId, ua.AttributeIds.NodeClass])
168
        desc = ua.ReferenceDescription()
169
        desc.DisplayName = attrs[0].Value.Value
170
        desc.BrowseName = attrs[1].Value.Value
171
        desc.NodeId = attrs[2].Value.Value
172
        desc.NodeClass = attrs[3].Value.Value
173
        desc.TypeDefinition = ua.TwoByteNodeId(ua.ObjectIds.FolderType)
174
        return desc
175
176
    def add_item(self, desc, parent=None, node=None):
177
        dname = bname = nodeid = "No Value"
178
        if desc.DisplayName:
179
            dname = desc.DisplayName.to_string()
180
        if desc.BrowseName:
181
            bname = desc.BrowseName.to_string()
182
        nodeid = desc.NodeId.to_string()
183
        item = [QStandardItem(dname), QStandardItem(bname), QStandardItem(nodeid)]
184
        if desc.NodeClass == ua.NodeClass.Object:
185
            if desc.TypeDefinition == ua.TwoByteNodeId(ua.ObjectIds.FolderType):
186
                item[0].setIcon(QIcon(":/folder.svg"))
187
            else:
188
                item[0].setIcon(QIcon(":/object.svg"))
189
        elif desc.NodeClass == ua.NodeClass.Variable:
190
            if desc.TypeDefinition == ua.TwoByteNodeId(ua.ObjectIds.PropertyType):
191
                item[0].setIcon(QIcon(":/property.svg"))
192
            else:
193
                item[0].setIcon(QIcon(":/variable.svg"))
194
        elif desc.NodeClass == ua.NodeClass.Method:
195
            item[0].setIcon(QIcon(":/method.svg"))
196
        elif desc.NodeClass == ua.NodeClass.ObjectType:
197
            item[0].setIcon(QIcon(":/object_type.svg"))
198
        elif desc.NodeClass == ua.NodeClass.VariableType:
199
            item[0].setIcon(QIcon(":/variable_type.svg"))
200
        elif desc.NodeClass == ua.NodeClass.DataType:
201
            item[0].setIcon(QIcon(":/data_type.svg"))
202
        elif desc.NodeClass == ua.NodeClass.ReferenceType:
203
            item[0].setIcon(QIcon(":/reference_type.svg"))
204
        if node:
205
            item[0].setData(node, Qt.UserRole)
206
        else:
207
            parent_node = parent.data(Qt.UserRole)
208
            item[0].setData(Node(parent_node.server, desc.NodeId), Qt.UserRole)
209
        if parent:
210
            return parent.appendRow(item)
211
        else:
212
            return self.appendRow(item)
213
214
    def reset_cache(self, node):
215
        if node in self._fetched:
216
            self._fetched.remove(node)
217
218
    def canFetchMore(self, idx):
219
        item = self.itemFromIndex(idx)
220
        if not item:
221
            return False
222
        node = item.data(Qt.UserRole)
223
        if node not in self._fetched:
224
            self._fetched.append(node)
225
            return True
226
        return False
227
228
    def hasChildren(self, idx):
229
        item = self.itemFromIndex(idx)
230
        if not item:
231
            return True
232
        node = item.data(Qt.UserRole)
233
        if node in self._fetched:
234
            return QStandardItemModel.hasChildren(self, idx)
235
        return True
236
237
    def fetchMore(self, idx):
238
        parent = self.itemFromIndex(idx)
239
        if parent:
240
            self._fetchMore(parent)
241
242
    def _fetchMore(self, parent):
243
        try:
244
            node = parent.data(Qt.UserRole)
245
            descs = node.get_children_descriptions()
246
            descs.sort(key=lambda x: x.BrowseName)
247
            added = []
248
            for desc in descs:
249
                if not desc.NodeId in added:
250
                    self.add_item(desc, parent)
251
                    added.append(desc.NodeId)
252
        except Exception as ex:
253
            self.error.emit(ex)
254
            raise
255
256
    def mimeData(self, idxs):
257
        mdata = QMimeData()
258
        nodes = []
259
        for idx in idxs:
260
            item = self.itemFromIndex(idx)
261
            if item:
262
                node = item.data(Qt.UserRole)
263
                if node:
264
                    nodes.append(node.nodeid.to_string())
265
        mdata.setText(", ".join(nodes))
266
        return mdata
267
268
269