Completed
Push — master ( 32dca3...0720ea )
by Olivier
52s
created

TreeWidget.set_current_node()   A

Complexity

Conditions 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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