]>
Commit | Line | Data |
---|---|---|
48723bbb JS |
1 | ## |
2 | ## This file is part of the sigrok-meter project. | |
3 | ## | |
4 | ## Copyright (C) 2014 Jens Steinhauser <jens.steinhauser@gmail.com> | |
5 | ## | |
6 | ## This program is free software; you can redistribute it and/or modify | |
7 | ## it under the terms of the GNU General Public License as published by | |
8 | ## the Free Software Foundation; either version 2 of the License, or | |
9 | ## (at your option) any later version. | |
10 | ## | |
11 | ## This program is distributed in the hope that it will be useful, | |
12 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | ## GNU General Public License for more details. | |
15 | ## | |
16 | ## You should have received a copy of the GNU General Public License | |
17 | ## along with this program; if not, write to the Free Software | |
18 | ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | ## | |
20 | ||
f76b9df8 | 21 | import collections |
48723bbb JS |
22 | import qtcompat |
23 | import sigrok.core as sr | |
f76b9df8 JS |
24 | import time |
25 | import util | |
48723bbb JS |
26 | |
27 | QtCore = qtcompat.QtCore | |
28 | QtGui = qtcompat.QtGui | |
29 | ||
30 | class MeasurementDataModel(QtGui.QStandardItemModel): | |
31 | '''Model to hold the measured values.''' | |
32 | ||
33 | '''Role used to identify and find the item.''' | |
f76b9df8 | 34 | idRole = QtCore.Qt.UserRole + 1 |
48723bbb JS |
35 | |
36 | '''Role used to store the device vendor and model.''' | |
37 | descRole = QtCore.Qt.UserRole + 2 | |
38 | ||
f76b9df8 JS |
39 | '''Role used to store past samples.''' |
40 | samplesRole = QtCore.Qt.UserRole + 3 | |
41 | ||
48723bbb JS |
42 | def __init__(self, parent): |
43 | super(self.__class__, self).__init__(parent) | |
44 | ||
45 | # Use the description text to sort the items for now, because the | |
f76b9df8 | 46 | # idRole holds tuples, and using them to sort doesn't work. |
48723bbb JS |
47 | self.setSortRole(MeasurementDataModel.descRole) |
48 | ||
49 | # Used in 'format_value()' to check against. | |
50 | self.inf = float('inf') | |
51 | ||
48723bbb JS |
52 | def format_mqflags(self, mqflags): |
53 | if sr.QuantityFlag.AC in mqflags: | |
54 | return 'AC' | |
55 | elif sr.QuantityFlag.DC in mqflags: | |
56 | return 'DC' | |
57 | else: | |
58 | return '' | |
59 | ||
60 | def format_value(self, mag): | |
61 | if mag == self.inf: | |
62 | return u'\u221E' | |
63 | return '{:f}'.format(mag) | |
64 | ||
65 | def getItem(self, device, channel): | |
480cdb7b UH |
66 | '''Return the item for the device + channel combination from the |
67 | model, or create a new item if no existing one matches.''' | |
48723bbb | 68 | |
480cdb7b UH |
69 | # Unique identifier for the device + channel. |
70 | # TODO: Isn't there something better? | |
48723bbb JS |
71 | uid = ( |
72 | device.vendor, | |
73 | device.model, | |
74 | device.serial_number(), | |
75 | device.connection_id(), | |
76 | channel.index | |
77 | ) | |
78 | ||
480cdb7b | 79 | # Find the correct item in the model. |
48723bbb JS |
80 | for row in range(self.rowCount()): |
81 | item = self.item(row) | |
f76b9df8 | 82 | rid = item.data(MeasurementDataModel.idRole) |
480cdb7b | 83 | rid = tuple(rid) # PySide returns a list. |
48723bbb JS |
84 | if uid == rid: |
85 | return item | |
86 | ||
480cdb7b | 87 | # Nothing found, create a new item. |
0e810ddf | 88 | desc = '{} {}, {}'.format( |
48723bbb JS |
89 | device.vendor, device.model, channel.name) |
90 | ||
91 | item = QtGui.QStandardItem() | |
f76b9df8 | 92 | item.setData(uid, MeasurementDataModel.idRole) |
48723bbb | 93 | item.setData(desc, MeasurementDataModel.descRole) |
f76b9df8 | 94 | item.setData(collections.defaultdict(list), MeasurementDataModel.samplesRole) |
48723bbb JS |
95 | self.appendRow(item) |
96 | self.sort(0) | |
97 | return item | |
98 | ||
99 | @QtCore.Slot(object, object, object) | |
100 | def update(self, device, channel, data): | |
480cdb7b | 101 | '''Update the data for the device (+channel) with the most recent |
48723bbb JS |
102 | measurement from the given payload.''' |
103 | ||
104 | item = self.getItem(device, channel) | |
105 | ||
106 | value, unit, mqflags = data | |
107 | value_str = self.format_value(value) | |
f76b9df8 | 108 | unit_str = util.format_unit(unit) |
48723bbb JS |
109 | mqflags_str = self.format_mqflags(mqflags) |
110 | ||
02862990 JS |
111 | # The display role is a tuple containing the value and the unit/flags. |
112 | disp = (value_str, ' '.join([unit_str, mqflags_str])) | |
48723bbb JS |
113 | item.setData(disp, QtCore.Qt.DisplayRole) |
114 | ||
f76b9df8 JS |
115 | # The samples role is a dictionary that contains the old samples for each unit. |
116 | # Should be trimmed periodically, otherwise it grows larger and larger. | |
117 | sample = (time.time(), value) | |
118 | d = item.data(MeasurementDataModel.samplesRole) | |
119 | d[unit].append(sample) | |
120 | ||
48723bbb JS |
121 | class MultimeterDelegate(QtGui.QStyledItemDelegate): |
122 | '''Delegate to show the data items from a MeasurementDataModel.''' | |
123 | ||
124 | def __init__(self, parent, font): | |
480cdb7b | 125 | '''Initialize the delegate. |
48723bbb JS |
126 | |
127 | :param font: Font used for the description text, the value is drawn | |
128 | with a slightly bigger and bold variant of the font. | |
129 | ''' | |
130 | ||
131 | super(self.__class__, self).__init__(parent) | |
132 | ||
133 | self._nfont = font | |
134 | self._bfont = QtGui.QFont(self._nfont) | |
135 | ||
136 | self._bfont.setBold(True) | |
137 | if self._bfont.pixelSize() != -1: | |
0e810ddf | 138 | self._bfont.setPixelSize(self._bfont.pixelSize() * 1.2) |
48723bbb | 139 | else: |
0e810ddf | 140 | self._bfont.setPointSizeF(self._bfont.pointSizeF() * 1.2) |
48723bbb JS |
141 | |
142 | fi = QtGui.QFontInfo(self._nfont) | |
143 | self._nfontheight = fi.pixelSize() | |
144 | ||
145 | fm = QtGui.QFontMetrics(self._bfont) | |
146 | r = fm.boundingRect('-XX.XXXXXX X XX') | |
0e810ddf | 147 | self._size = QtCore.QSize(r.width() * 1.4, r.height() * 2.2) |
02862990 JS |
148 | |
149 | # Values used to calculate the positions of the strings in the | |
150 | # 'paint()' function. | |
151 | self._space_width = fm.boundingRect('_').width() | |
152 | self._value_width = fm.boundingRect('-XX.XXXXXX').width() | |
48723bbb JS |
153 | |
154 | def sizeHint(self, option=None, index=None): | |
155 | return self._size | |
156 | ||
157 | def paint(self, painter, options, index): | |
02862990 | 158 | value, unit = index.data(QtCore.Qt.DisplayRole) |
48723bbb JS |
159 | desc = index.data(MeasurementDataModel.descRole) |
160 | ||
48723bbb JS |
161 | painter.setFont(self._nfont) |
162 | p = options.rect.topLeft() | |
163 | p += QtCore.QPoint(self._nfontheight, 2 * self._nfontheight) | |
0e810ddf | 164 | painter.drawText(p, desc + ': ' + value + ' ' + unit) |