Completed
Push — master ( 846094...ff6e4b )
by Olivier
01:51
created

TreeViewModel.reset_cache()   A

Complexity

Conditions 2

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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