Completed
Push — master ( 8ef529...d1d067 )
by Olivier
01:10
created

TreeWidget.start()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 4
rs 10
1
from PyQt5.QtCore import pyqtSignal, QMimeData, QObject
2
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QIcon
3
from PyQt5.QtWidgets import QApplication, QAbstractItemView
4
5
from opcua import ua
6
7
8
class TreeWidget(QObject):
9
10
    error = pyqtSignal(str)
11
12
    def __init__(self, view):
13
        QObject.__init__(self, view)
14
        self.view = view
15
        self.model = TreeViewModel()
16
        self.model.clear()  # FIXME: do we need this?
17
        #self.model.error.connect(lambda x: self.error.emit(x))
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(1)
23
24
    def clear(self):
25
        self.model.clear()
26
27
    def start(self, uaclient):
28
        self.model.clear()
29
        self.model.set_uaclient(uaclient)
30
        self.model.add_item(self._get_root_desc(uaclient))
31
32
    def _get_root_desc(self, uaclient):
33
        node = uaclient.get_root_node()
34
        attrs = node.get_attributes([ua.AttributeIds.DisplayName, ua.AttributeIds.BrowseName, ua.AttributeIds.NodeId, ua.AttributeIds.NodeClass])
35
        desc = ua.ReferenceDescription()
36
        desc.DisplayName = attrs[0].Value.Value
37
        desc.BrowseName = attrs[1].Value.Value
38
        desc.NodeId = attrs[2].Value.Value
39
        desc.NodeClass = attrs[3].Value.Value
40
        desc.TypeDefinition = ua.TwoByteNodeId(ua.ObjectIds.FolderType)
41
        return desc
42
43
    def copy_path(self):
44
        path = self.get_current_path()
45
        path_str = ",".join(path)
46
        QApplication.clipboard().setText(path_str)
47
48
    def copy_nodeid(self):
49
        node = self.get_current_node()
50
        if node:
51
            text = node.nodeid.to_string()
52
        else:
53
            text = ""
54
        QApplication.clipboard().setText(text)
55
56
    def get_current_path(self):
57
        idx = self.view.currentIndex()
58
        idx = idx.sibling(idx.row(), 0)
59
        it = self.model.itemFromIndex(idx)
60
        path = []
61
        while it and it.data():
62
            node = it.data()
63
            name = node.get_browse_name().to_string()
64
            path.insert(0, name)
65
            it = it.parent()
66
        return path
67
68
    def get_current_node(self, idx=None):
69
        if idx is None:
70
            idx = self.view.currentIndex()
71
        idx = idx.sibling(idx.row(), 0)
72
        it = self.model.itemFromIndex(idx)
73
        if not it:
74
            return None
75
        node = it.data()
76
        if not node:
77
            print("No node for item:", it, it.text())
78
            return None
79
        return node
80
81
82
class TreeViewModel(QStandardItemModel):
83
84
    error = pyqtSignal(str)
85
86
    def __init__(self):
87
        super(TreeViewModel, self).__init__()
88
        self.uaclient = None
89
        self._fetched = []
90
91
    def set_uaclient(self, uaclient):
92
        self.uaclient = uaclient
93
94
    def clear(self):
95
        QStandardItemModel.clear(self)
96
        self._fetched = []
97
        self.setHorizontalHeaderLabels(['DisplayName', "BrowseName", 'NodeId'])
98
99
    def add_item(self, desc, parent=None):
100
        item = [QStandardItem(desc.DisplayName.to_string()), QStandardItem(desc.BrowseName.to_string()), QStandardItem(desc.NodeId.to_string())]
101
        if desc.NodeClass == ua.NodeClass.Object:
102
            if desc.TypeDefinition == ua.TwoByteNodeId(ua.ObjectIds.FolderType):
103
                item[0].setIcon(QIcon(":/folder.svg"))
104
            else:
105
                item[0].setIcon(QIcon(":/object.svg"))
106
        elif desc.NodeClass == ua.NodeClass.Variable:
107
            if desc.TypeDefinition == ua.TwoByteNodeId(ua.ObjectIds.PropertyType):
108
                item[0].setIcon(QIcon(":/property.svg"))
109
            else:
110
                item[0].setIcon(QIcon(":/variable.svg"))
111
        elif desc.NodeClass == ua.NodeClass.Method:
112
            item[0].setIcon(QIcon(":/method.svg"))
113
114
        item[0].setData(self.uaclient.get_node(desc.NodeId))
115
        if parent:
116
            return parent.appendRow(item)
117
        else:
118
            return self.appendRow(item)
119
120
    def canFetchMore(self, idx):
121
        item = self.itemFromIndex(idx)
122
        if not item:
123
            return True
124
        node = item.data()
125
        if node not in self._fetched:
126
            self._fetched.append(node)
127
            return True
128
        return False
129
130
    def hasChildren(self, idx):
131
        item = self.itemFromIndex(idx)
132
        if not item:
133
            return True
134
        node = item.data()
135
        if node in self._fetched:
136
            return QStandardItemModel.hasChildren(self, idx)
137
        return True
138
139
    def fetchMore(self, idx):
140
        parent = self.itemFromIndex(idx)
141
        if parent:
142
            self._fetchMore(parent)
143
144
    def _fetchMore(self, parent):
145
        try:
146
            descs = parent.data().get_children_descriptions()
147
            for desc in descs:
148
                self.add_item(desc, parent)
149
        except Exception as ex:
150
            self.error.emit(ex)
151
            raise
152
153
    #def flags(self, idx):
154
        #item = self.itemFromIndex(idx)
155
        #flags = QStandardItemModel.flags(self, idx)
156
        #if not item:
157
            #return flags
158
        #node = item.data()
159
        #if node and node.get_node_class() == ua.NodeClass.Variable:
160
            ## FIXME not efficient to query, should be stored in data()
161
            ##print(1, flags)
162
            #return flags | Qt.ItemIsDropEnabled
163
        #else:
164
            #print(2, flags)
165
            #return flags
166
167
    #def mimeTypes(self):
168
        #return ["application/vnd.text.list"]
169
170
    def mimeData(self, idxs):
171
        mdata = QMimeData()
172
        nodes = []
173
        for idx in idxs:
174
            item = self.itemFromIndex(idx)
175
            if item:
176
                node = item.data()
177
                if node:
178
                    nodes.append(node.nodeid.to_string())
179
        mdata.setText(", ".join(nodes))
180
        return mdata
181
182
183