]>
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 |
3010b5a0 | 22 | import itertools |
48723bbb JS |
23 | import qtcompat |
24 | import sigrok.core as sr | |
f76b9df8 JS |
25 | import time |
26 | import util | |
48723bbb | 27 | |
3010b5a0 JS |
28 | try: |
29 | from itertools import izip | |
30 | except ImportError: | |
31 | izip = zip | |
32 | ||
48723bbb JS |
33 | QtCore = qtcompat.QtCore |
34 | QtGui = qtcompat.QtGui | |
35 | ||
36 | class MeasurementDataModel(QtGui.QStandardItemModel): | |
37 | '''Model to hold the measured values.''' | |
38 | ||
39 | '''Role used to identify and find the item.''' | |
f76b9df8 | 40 | idRole = QtCore.Qt.UserRole + 1 |
48723bbb JS |
41 | |
42 | '''Role used to store the device vendor and model.''' | |
43 | descRole = QtCore.Qt.UserRole + 2 | |
44 | ||
f76b9df8 JS |
45 | '''Role used to store past samples.''' |
46 | samplesRole = QtCore.Qt.UserRole + 3 | |
47 | ||
3010b5a0 JS |
48 | '''Role used to store the color to draw the graph of the channel.''' |
49 | colorRole = QtCore.Qt.UserRole + 4 | |
50 | ||
48723bbb JS |
51 | def __init__(self, parent): |
52 | super(self.__class__, self).__init__(parent) | |
53 | ||
54 | # Use the description text to sort the items for now, because the | |
f76b9df8 | 55 | # idRole holds tuples, and using them to sort doesn't work. |
48723bbb JS |
56 | self.setSortRole(MeasurementDataModel.descRole) |
57 | ||
58 | # Used in 'format_value()' to check against. | |
59 | self.inf = float('inf') | |
60 | ||
3010b5a0 JS |
61 | # A generator for the colors of the channels. |
62 | self._colorgen = self._make_colorgen() | |
63 | ||
64 | def _make_colorgen(self): | |
65 | cols = [ | |
66 | QtGui.QColor(0x8F, 0x52, 0x02), # brown | |
67 | QtGui.QColor(0xCC, 0x00, 0x00), # red | |
68 | QtGui.QColor(0xF5, 0x79, 0x00), # orange | |
69 | QtGui.QColor(0xED, 0xD4, 0x00), # yellow | |
70 | QtGui.QColor(0x73, 0xD2, 0x16), # green | |
71 | QtGui.QColor(0x34, 0x65, 0xA4), # blue | |
72 | QtGui.QColor(0x75, 0x50, 0x7B) # violet | |
73 | ] | |
74 | ||
75 | def myrepeat(g, n): | |
76 | '''Repeats every element from 'g' 'n' times'.''' | |
77 | for e in g: | |
78 | for f in itertools.repeat(e, n): | |
79 | yield f | |
80 | ||
81 | colorcycle = itertools.cycle(cols) | |
82 | darkness = myrepeat(itertools.count(100, 10), len(cols)) | |
83 | ||
84 | for c, d in izip(colorcycle, darkness): | |
85 | yield QtGui.QColor(c).darker(d) | |
86 | ||
48723bbb JS |
87 | def format_mqflags(self, mqflags): |
88 | if sr.QuantityFlag.AC in mqflags: | |
89 | return 'AC' | |
90 | elif sr.QuantityFlag.DC in mqflags: | |
91 | return 'DC' | |
92 | else: | |
93 | return '' | |
94 | ||
95 | def format_value(self, mag): | |
96 | if mag == self.inf: | |
97 | return u'\u221E' | |
98 | return '{:f}'.format(mag) | |
99 | ||
100 | def getItem(self, device, channel): | |
480cdb7b UH |
101 | '''Return the item for the device + channel combination from the |
102 | model, or create a new item if no existing one matches.''' | |
48723bbb | 103 | |
480cdb7b UH |
104 | # Unique identifier for the device + channel. |
105 | # TODO: Isn't there something better? | |
48723bbb JS |
106 | uid = ( |
107 | device.vendor, | |
108 | device.model, | |
109 | device.serial_number(), | |
110 | device.connection_id(), | |
111 | channel.index | |
112 | ) | |
113 | ||
480cdb7b | 114 | # Find the correct item in the model. |
48723bbb JS |
115 | for row in range(self.rowCount()): |
116 | item = self.item(row) | |
f76b9df8 | 117 | rid = item.data(MeasurementDataModel.idRole) |
480cdb7b | 118 | rid = tuple(rid) # PySide returns a list. |
48723bbb JS |
119 | if uid == rid: |
120 | return item | |
121 | ||
480cdb7b | 122 | # Nothing found, create a new item. |
0e810ddf | 123 | desc = '{} {}, {}'.format( |
48723bbb JS |
124 | device.vendor, device.model, channel.name) |
125 | ||
126 | item = QtGui.QStandardItem() | |
f76b9df8 | 127 | item.setData(uid, MeasurementDataModel.idRole) |
48723bbb | 128 | item.setData(desc, MeasurementDataModel.descRole) |
f76b9df8 | 129 | item.setData(collections.defaultdict(list), MeasurementDataModel.samplesRole) |
3010b5a0 | 130 | item.setData(next(self._colorgen), MeasurementDataModel.colorRole) |
48723bbb JS |
131 | self.appendRow(item) |
132 | self.sort(0) | |
133 | return item | |
134 | ||
135 | @QtCore.Slot(object, object, object) | |
136 | def update(self, device, channel, data): | |
480cdb7b | 137 | '''Update the data for the device (+channel) with the most recent |
48723bbb JS |
138 | measurement from the given payload.''' |
139 | ||
140 | item = self.getItem(device, channel) | |
141 | ||
142 | value, unit, mqflags = data | |
143 | value_str = self.format_value(value) | |
f76b9df8 | 144 | unit_str = util.format_unit(unit) |
48723bbb JS |
145 | mqflags_str = self.format_mqflags(mqflags) |
146 | ||
02862990 JS |
147 | # The display role is a tuple containing the value and the unit/flags. |
148 | disp = (value_str, ' '.join([unit_str, mqflags_str])) | |
48723bbb JS |
149 | item.setData(disp, QtCore.Qt.DisplayRole) |
150 | ||
f76b9df8 JS |
151 | # The samples role is a dictionary that contains the old samples for each unit. |
152 | # Should be trimmed periodically, otherwise it grows larger and larger. | |
153 | sample = (time.time(), value) | |
154 | d = item.data(MeasurementDataModel.samplesRole) | |
155 | d[unit].append(sample) | |
156 | ||
48723bbb JS |
157 | class MultimeterDelegate(QtGui.QStyledItemDelegate): |
158 | '''Delegate to show the data items from a MeasurementDataModel.''' | |
159 | ||
160 | def __init__(self, parent, font): | |
480cdb7b | 161 | '''Initialize the delegate. |
48723bbb JS |
162 | |
163 | :param font: Font used for the description text, the value is drawn | |
164 | with a slightly bigger and bold variant of the font. | |
165 | ''' | |
166 | ||
167 | super(self.__class__, self).__init__(parent) | |
168 | ||
169 | self._nfont = font | |
170 | self._bfont = QtGui.QFont(self._nfont) | |
171 | ||
172 | self._bfont.setBold(True) | |
173 | if self._bfont.pixelSize() != -1: | |
0e810ddf | 174 | self._bfont.setPixelSize(self._bfont.pixelSize() * 1.2) |
48723bbb | 175 | else: |
0e810ddf | 176 | self._bfont.setPointSizeF(self._bfont.pointSizeF() * 1.2) |
48723bbb JS |
177 | |
178 | fi = QtGui.QFontInfo(self._nfont) | |
179 | self._nfontheight = fi.pixelSize() | |
180 | ||
181 | fm = QtGui.QFontMetrics(self._bfont) | |
182 | r = fm.boundingRect('-XX.XXXXXX X XX') | |
0e810ddf | 183 | self._size = QtCore.QSize(r.width() * 1.4, r.height() * 2.2) |
02862990 JS |
184 | |
185 | # Values used to calculate the positions of the strings in the | |
186 | # 'paint()' function. | |
187 | self._space_width = fm.boundingRect('_').width() | |
188 | self._value_width = fm.boundingRect('-XX.XXXXXX').width() | |
48723bbb JS |
189 | |
190 | def sizeHint(self, option=None, index=None): | |
191 | return self._size | |
192 | ||
193 | def paint(self, painter, options, index): | |
02862990 | 194 | value, unit = index.data(QtCore.Qt.DisplayRole) |
48723bbb JS |
195 | desc = index.data(MeasurementDataModel.descRole) |
196 | ||
48723bbb JS |
197 | painter.setFont(self._nfont) |
198 | p = options.rect.topLeft() | |
199 | p += QtCore.QPoint(self._nfontheight, 2 * self._nfontheight) | |
0e810ddf | 200 | painter.drawText(p, desc + ': ' + value + ' ' + unit) |