Completed
Push — master ( 55bf40...8b993b )
by Olivier
56s
created

TreeWidget.expand_current_node()   A

Complexity

Conditions 1

Size

Total Lines 6

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 6
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
        self.view.activated.emit(idx)
52
53
    def expand_current_node(self, expand=True):
54
        """
55
        this function is meant to be used in tests
56
        """
57
        idx = self.view.currentIndex()
58
        self.view.setExpanded(idx, expand)
59
60
    def copy_nodeid(self):
61
        node = self.get_current_node()
62
        text = node.nodeid.to_string()
63
        QApplication.clipboard().setText(text)
64
65
    def get_current_path(self):
66
        idx = self.view.currentIndex()
67
        idx = idx.sibling(idx.row(), 0)
68
        it = self.model.itemFromIndex(idx)
69
        path = []
70
        while it and it.data(Qt.UserRole):
71
            node = it.data(Qt.UserRole)
72
            name = node.get_browse_name().to_string()
73
            path.insert(0, name)
74
            it = it.parent()
75
        return path
76
77
    def update_browse_name_current_item(self, bname):
78
        idx = self.view.currentIndex()
79
        idx = idx.sibling(idx.row(), 1)
80
        it = self.model.itemFromIndex(idx)
81
        it.setText(bname.to_string())
82
83
    def update_display_name_current_item(self, dname):
84
        idx = self.view.currentIndex()
85
        idx = idx.sibling(idx.row(), 0)
86
        it = self.model.itemFromIndex(idx)
87
        it.setText(dname.Text.decode())
88
89
    def reload_current(self):
90
        idx = self.view.currentIndex()
91
        idx = idx.sibling(idx.row(), 0)
92
        it = self.model.itemFromIndex(idx)
93
        if not it:
94
            return None
95
        self.reload(it)
96
97
    def reload(self, item=None):
98
        if item is None:
99
            item = self.model.item(0, 0)
100
        for _ in range(item.rowCount()):
101
            child_it = item.child(0, 0)
102
            node = child_it.data(Qt.UserRole)
103
            if node:
104
                self.model.reload(node)
105
            item.takeRow(0)
106
        node = item.data(Qt.UserRole)
107
        if node:
108
            self.model.reload(node)
109
110
    def remove_current_item(self):
111
        idx = self.view.currentIndex()
112
        self.model.removeRow(idx.row(), idx.parent())
113
114
    def get_current_node(self, idx=None):
115
        if idx is None:
116
            idx = self.view.currentIndex()
117
        idx = idx.sibling(idx.row(), 0)
118
        it = self.model.itemFromIndex(idx)
119
        if not it:
120
            return None
121
        node = it.data(Qt.UserRole)
122
        if not node:
123
            self.error.emit("Item does not contain node data, report!")
124
            raise RuntimeError("Item does not contain node data, report!")
125
        return node
126
127
128
class TreeViewModel(QStandardItemModel):
129
130
    error = pyqtSignal(str)
131
132
    def __init__(self):
133
        super(TreeViewModel, self).__init__()
134
        self._fetched = []
135
136
    def clear(self):
137
        QStandardItemModel.clear(self)
138
        self._fetched = []
139
        self.setHorizontalHeaderLabels(['DisplayName', "BrowseName", 'NodeId'])
140
141
    def set_root_node(self, node):
142
        desc = self._get_node_desc(node)
143
        self.add_item(desc, node=node)
144
145
    def _get_node_desc(self, node):
146
        attrs = node.get_attributes([ua.AttributeIds.DisplayName, ua.AttributeIds.BrowseName, ua.AttributeIds.NodeId, ua.AttributeIds.NodeClass])
147
        desc = ua.ReferenceDescription()
148
        desc.DisplayName = attrs[0].Value.Value
149
        desc.BrowseName = attrs[1].Value.Value
150
        desc.NodeId = attrs[2].Value.Value
151
        desc.NodeClass = attrs[3].Value.Value
152
        desc.TypeDefinition = ua.TwoByteNodeId(ua.ObjectIds.FolderType)
153
        return desc
154
155
    def add_item(self, desc, parent=None, node=None):
156
        item = [QStandardItem(desc.DisplayName.to_string()), QStandardItem(desc.BrowseName.to_string()), QStandardItem(desc.NodeId.to_string())]
157
        if desc.NodeClass == ua.NodeClass.Object:
158
            if desc.TypeDefinition == ua.TwoByteNodeId(ua.ObjectIds.FolderType):
159
                item[0].setIcon(QIcon(":/folder.svg"))
160
            else:
161
                item[0].setIcon(QIcon(":/object.svg"))
162
        elif desc.NodeClass == ua.NodeClass.Variable:
163
            if desc.TypeDefinition == ua.TwoByteNodeId(ua.ObjectIds.PropertyType):
164
                item[0].setIcon(QIcon(":/property.svg"))
165
            else:
166
                item[0].setIcon(QIcon(":/variable.svg"))
167
        elif desc.NodeClass == ua.NodeClass.Method:
168
            item[0].setIcon(QIcon(":/method.svg"))
169
        elif desc.NodeClass == ua.NodeClass.ObjectType:
170
            item[0].setIcon(QIcon(":/object_type.svg"))
171
        elif desc.NodeClass == ua.NodeClass.VariableType:
172
            item[0].setIcon(QIcon(":/variable_type.svg"))
173
        elif desc.NodeClass == ua.NodeClass.DataType:
174
            item[0].setIcon(QIcon(":/data_type.svg"))
175
        elif desc.NodeClass == ua.NodeClass.ReferenceType:
176
            item[0].setIcon(QIcon(":/reference_type.svg"))
177
        if node:
178
            item[0].setData(node, Qt.UserRole)
179
        else:
180
            parent_node = parent.data(Qt.UserRole)
181
            item[0].setData(Node(parent_node.server, desc.NodeId), Qt.UserRole)
182
        if parent:
183
            return parent.appendRow(item)
184
        else:
185
            return self.appendRow(item)
186
187
    def reload(self, node):
188
        if node in self._fetched:
189
            self._fetched.remove(node)
190
191
    def canFetchMore(self, idx):
192
        item = self.itemFromIndex(idx)
193
        if not item:
194
            return True
195
        node = item.data(Qt.UserRole)
196
        if node not in self._fetched:
197
            self._fetched.append(node)
198
            return True
199
        return False
200
201
    def hasChildren(self, idx):
202
        item = self.itemFromIndex(idx)
203
        if not item:
204
            return True
205
        node = item.data(Qt.UserRole)
206
        if node in self._fetched:
207
            return QStandardItemModel.hasChildren(self, idx)
208
        return True
209
210
    def fetchMore(self, idx):
211
        parent = self.itemFromIndex(idx)
212
        if parent:
213
            self._fetchMore(parent)
214
215
    def _fetchMore(self, parent):
216
        try:
217
            descs = parent.data(Qt.UserRole).get_children_descriptions()
218
            descs.sort(key=lambda x: x.BrowseName)
219
            for desc in descs:
220
                self.add_item(desc, parent)
221
        except Exception as ex:
222
            self.error.emit(ex)
223
            raise
224
225
    def mimeData(self, idxs):
226
        mdata = QMimeData()
227
        nodes = []
228
        for idx in idxs:
229
            item = self.itemFromIndex(idx)
230
            if item:
231
                node = item.data(Qt.UserRole)
232
                if node:
233
                    nodes.append(node.nodeid.to_string())
234
        mdata.setText(", ".join(nodes))
235
        return mdata
236
237
238