Completed
Pull Request — master (#4)
by
unknown
56s
created

MyDelegate   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 79
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 1
Metric Value
c 4
b 0
f 1
dl 0
loc 79
rs 10
wmc 16

3 Methods

Rating   Name   Duplication   Size   Complexity  
D createEditor() 0 34 8
A __init__() 0 3 1
C setModelData() 0 33 7
1
from datetime import datetime
2
from PyQt5.QtCore import pyqtSignal, Qt, QObject, QSettings
3
from PyQt5.QtGui import QStandardItemModel, QStandardItem
4
from PyQt5.QtWidgets import QApplication, QMenu, QAction, QStyledItemDelegate, QComboBox, QWidget, QVBoxLayout, QCheckBox, QDialog
5
6
from opcua import ua
7
from opcua import Node
8
from opcua.common.ua_utils import string_to_variant, variant_to_string, val_to_string
9
10
from uawidgets.get_node_dialog import GetNodeButton
11
12
13
class BitEditor(QDialog):
14
    """
15
    Edit bits in data
16
    FIXME: this should be a dialog but a Widget appearing directly in treewidget
17
    Patch welcome
18
    """
19
20
    def __init__(self, parent, attr, val):
21
        QDialog.__init__(self, parent)
22
        layout = QVBoxLayout(self)
23
        self.setLayout(layout)
24
        self.boxes = []
25
        self.enum = attr_to_enum(attr)
26
        for el in self.enum:
27
            box = QCheckBox(el.name, parent)
28
            layout.addWidget(box)
29
            self.boxes.append(box)
30
            if ua.test_bit(val, el.value):
31
                box.setChecked(True)
32
            else:
33
                box.setChecked(False)
34
35
    def get_byte(self):
36
        data = 0
37
        for box in self.boxes:
38
            if box.isChecked():
39
                data = ua.set_bit(data, self.enum[box.text()].value)
40
        return data
41
42
43
class AttrsWidget(QObject):
44
45
    error = pyqtSignal(Exception)
46
    modified = pyqtSignal()
47
48
    def __init__(self, view, show_timestamps=True):
49
        QObject.__init__(self, view)
50
        self.view = view
51
        self._timestamps = show_timestamps
52
        delegate = MyDelegate(self.view, self)
53
        delegate.error.connect(self.error.emit)
54
        self.settings = QSettings()
55
        self.view.setItemDelegate(delegate)
56
        self.model = QStandardItemModel()
57
        self.model.setHorizontalHeaderLabels(['Attribute', 'Value', 'DataType'])
58
        state = self.settings.value("attrs_widget", None)
59
        if state is not None:
60
            self.view.header().restoreState(state)
61
        self.view.setModel(self.model)
62
        self.current_node = None
63
        self.model.itemChanged.connect(self._item_changed)
64
        self.view.header().setSectionResizeMode(0)
65
        self.view.header().setStretchLastSection(True)
66
67
        # Context menu
68
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
69
        self.view.customContextMenuRequested.connect(self.showContextMenu)
70
        copyaction = QAction("&Copy Value", self.model)
71
        copyaction.triggered.connect(self._copy_value)
72
        self._contextMenu = QMenu()
73
        self._contextMenu.addAction(copyaction)
74
75
    def save_state(self):
76
        self.settings.setValue("attrs_widget", self.view.header().saveState())
77
78
    def _item_changed(self, item):
79
        attr, dv = item.data(Qt.UserRole)
80
        if attr == ua.AttributeIds.Value:
81
            dv.SourceTimestamp = datetime.now()
82
        try:
83
            self.current_node.set_attribute(attr, dv)
84
        except Exception as ex:
85
            self.error.emit(ex)
86
            raise
87
        if attr == ua.AttributeIds.Value:
88
            it = self.model.item(item.index().row(), 0)
89
            self._show_timestamps(it, dv)
90
        self.modified.emit()
91
92
    def showContextMenu(self, position):
93
        item = self.get_current_item()
94
        if item:
95
            self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))
96
97
    def get_current_item(self, col_idx=0):
98
        idx = self.view.currentIndex()
99
        return self.model.item(idx.row(), col_idx)
100
101
    def _copy_value(self, position):
102
        it = self.get_current_item(1)
103
        if it:
104
            QApplication.clipboard().setText(it.text())
105
106
    def clear(self):
107
        # remove all rows but not header!!
108
        self.model.removeRows(0, self.model.rowCount())
109
110
    def reload(self):
111
        self.show_attrs(self.current_node)
112
113
    def show_attrs(self, node):
114
        self.current_node = node
115
        self.clear()
116
        if self.current_node:
117
            self._show_attrs()
118
        self.view.expandAll()
119
120
    def _show_attrs(self):
121
        attrs = self.get_all_attrs()
122
        for attr, dv in attrs:
123
            if attr == ua.AttributeIds.DataType:
124
                string = data_type_to_string(dv)
125
            elif attr in (ua.AttributeIds.AccessLevel,
126
                          ua.AttributeIds.UserAccessLevel,
127
                          ua.AttributeIds.WriteMask,
128
                          ua.AttributeIds.UserWriteMask,
129
                          ua.AttributeIds.EventNotifier):
130
                string = enum_to_string(attr, dv)
131
            else:
132
                string = variant_to_string(dv.Value)
133
            name_item = QStandardItem(attr.name)
134
            vitem = QStandardItem(string)
135
            vitem.setData((attr, dv), Qt.UserRole)
136
            self.model.appendRow([name_item, vitem, QStandardItem(dv.Value.VariantType.name)])
137
138
            if self._timestamps and attr == ua.AttributeIds.Value:
139
                self._show_timestamps(name_item, dv)
140
141
    def _show_timestamps(self, item, dv):
142
        while item.hasChildren():
143
            self.model.removeRow(0, item.index())
144
        string = val_to_string(dv.ServerTimestamp)
145
        item.appendRow([QStandardItem("Server Timestamp"), QStandardItem(string), QStandardItem(ua.VariantType.DateTime.name)])
146
        string = val_to_string(dv.SourceTimestamp)
147
        item.appendRow([QStandardItem("Source Timestamp"), QStandardItem(string), QStandardItem(ua.VariantType.DateTime.name)])
148
149
150
    def get_all_attrs(self):
151
        attrs = [attr for attr in ua.AttributeIds]
152
        try:
153
            dvs = self.current_node.get_attributes(attrs)
154
        except Exception as ex:
155
            self.error.emit(ex)
156
            raise
157
        res = []
158
        for idx, dv in enumerate(dvs):
159
            if dv.StatusCode.is_good():
160
                res.append((attrs[idx], dv))
161
        res.sort(key=lambda x: x[0].name)
162
        return res
163
164
165
class MyDelegate(QStyledItemDelegate):
166
167
    error = pyqtSignal(Exception)
168
169
    def __init__(self, parent, attrs_widget):
170
        QStyledItemDelegate.__init__(self, parent)
171
        self.attrs_widget = attrs_widget
172
173
    def createEditor(self, parent, option, idx):
174
        if idx.column() != 1:
175
            return None
176
        item = self.attrs_widget.model.itemFromIndex(idx)
177
        attr, dv = item.data(Qt.UserRole)
178
        text = item.text()
179
        if attr == ua.AttributeIds.NodeId:
180
            return None
181
        if dv.Value.VariantType == ua.VariantType.Boolean:
182
            combo = QComboBox(parent)
183
            combo.addItem("True")
184
            combo.addItem("False")
185
            combo.setCurrentText(text)
186
            return combo
187
        elif attr == ua.AttributeIds.NodeClass:
188
            combo = QComboBox(parent)
189
            for nclass in ua.NodeClass:
190
                combo.addItem(nclass.name)
191
            combo.setCurrentText(text)
192
            return combo
193
        elif attr == ua.AttributeIds.DataType:
194
            nodeid = getattr(ua.ObjectIds, text)
195
            node = Node(self.attrs_widget.current_node.server, nodeid)
196
            startnode = Node(self.attrs_widget.current_node.server, ua.ObjectIds.BaseDataType)
197
            button = GetNodeButton(parent, node, startnode)
198
            return button
199
        elif attr in (ua.AttributeIds.AccessLevel,
200
                      ua.AttributeIds.UserAccessLevel,
201
                      ua.AttributeIds.WriteMask,
202
                      ua.AttributeIds.UserWriteMask,
203
                      ua.AttributeIds.EventNotifier):
204
            return BitEditor(parent, attr, dv.Value.Value)
205
        else:
206
            return QStyledItemDelegate.createEditor(self, parent, option, idx)
207
208
    #def setEditorData(self, editor, index):
209
        #pass
210
211
    def setModelData(self, editor, model, idx):
212
        #item = self.attrs_widget.model.itemFromIndex(idx)
213
        attr, dv = model.data(idx, Qt.UserRole)
214
        if attr == ua.AttributeIds.NodeClass:
215
            dv.Value.Value = ua.NodeClass[editor.currentText()]
216
            text = editor.currentText()
217
        elif attr == ua.AttributeIds.DataType:
218
            dv.Value.Value = editor.get_node().nodeid
219
            text = data_type_to_string(dv)
220
        elif attr in (ua.AttributeIds.AccessLevel,
221
                      ua.AttributeIds.UserAccessLevel,
222
                      ua.AttributeIds.WriteMask,
223
                      ua.AttributeIds.UserWriteMask,
224
                      ua.AttributeIds.EventNotifier):
225
            dv.Value.Value = editor.get_byte()
226
            text = enum_to_string(attr, dv)
227
        else:
228
            if isinstance(editor, QComboBox):
229
                text = editor.currentText()
230
            else:
231
                text = editor.text()
232
233
            try:
234
                # if user is setting a value on a null variant, try using the nodes datatype instead
235
                if dv.Value.VariantType is ua.VariantType.Null:
236
                    dtype = self.attrs_widget.current_node.get_data_type_as_variant_type()
237
                    dv.Value = string_to_variant(text, dtype)
238
                else:
239
                    dv.Value = string_to_variant(text, dv.Value.VariantType)
240
            except Exception as ex:
241
                self.error.emit(ex)
242
                raise
243
        model.setItemData(idx, {Qt.DisplayRole: text, Qt.UserRole: (attr, dv)})
244
245
246
def data_type_to_string(dv):
247
    # a bit too complex, we could just display browse name of node but it requires a query
248
    if isinstance(dv.Value.Value.Identifier, int) and dv.Value.Value.Identifier < 63:
249
        string = ua.datatype_to_varianttype(dv.Value.Value).name
250
    elif dv.Value.Value.Identifier in ua.ObjectIdNames:
251
        string = ua.ObjectIdNames[dv.Value.Value.Identifier]
252
    else:
253
        string = dv.Value.Value.to_string()
254
    return string
255
256
257
def attr_to_enum(attr):
258
    attr_name = attr.name
259
    if attr_name.startswith("User"):
260
        attr_name = attr_name[4:]
261
    return getattr(ua, attr_name)
262
263
264
def enum_to_string(attr, dv):
265
    attr_enum = attr_to_enum(attr)
266
    string = ", ".join([e.name for e in attr_enum.parse_bitfield(dv.Value.Value)])
267
    return string
268