]>
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 | ||
21 | import qtcompat | |
22 | import sigrok.core as sr | |
23 | ||
24 | QtCore = qtcompat.QtCore | |
25 | QtGui = qtcompat.QtGui | |
26 | ||
27 | class MeasurementDataModel(QtGui.QStandardItemModel): | |
28 | '''Model to hold the measured values.''' | |
29 | ||
30 | '''Role used to identify and find the item.''' | |
31 | _idRole = QtCore.Qt.UserRole + 1 | |
32 | ||
33 | '''Role used to store the device vendor and model.''' | |
34 | descRole = QtCore.Qt.UserRole + 2 | |
35 | ||
36 | def __init__(self, parent): | |
37 | super(self.__class__, self).__init__(parent) | |
38 | ||
39 | # Use the description text to sort the items for now, because the | |
40 | # _idRole holds tuples, and using them to sort doesn't work. | |
41 | self.setSortRole(MeasurementDataModel.descRole) | |
42 | ||
43 | # Used in 'format_value()' to check against. | |
44 | self.inf = float('inf') | |
45 | ||
46 | def format_unit(self, u): | |
47 | units = { | |
48 | sr.Unit.VOLT: 'V', | |
49 | sr.Unit.AMPERE: 'A', | |
50 | sr.Unit.OHM: u'\u03A9', | |
51 | sr.Unit.FARAD: 'F', | |
52 | sr.Unit.KELVIN: 'K', | |
53 | sr.Unit.CELSIUS: u'\u00B0C', | |
54 | sr.Unit.FAHRENHEIT: u'\u00B0F', | |
55 | sr.Unit.HERTZ: 'Hz', | |
56 | sr.Unit.PERCENTAGE: '%', | |
57 | # sr.Unit.BOOLEAN | |
58 | sr.Unit.SECOND: 's', | |
59 | sr.Unit.SIEMENS: 'S', | |
60 | sr.Unit.DECIBEL_MW: 'dBu', | |
61 | sr.Unit.DECIBEL_VOLT: 'dBV', | |
62 | # sr.Unit.UNITLESS | |
63 | sr.Unit.DECIBEL_SPL: 'dB', | |
64 | # sr.Unit.CONCENTRATION | |
65 | sr.Unit.REVOLUTIONS_PER_MINUTE: 'rpm', | |
66 | sr.Unit.VOLT_AMPERE: 'VA', | |
67 | sr.Unit.WATT: 'W', | |
68 | sr.Unit.WATT_HOUR: 'Wh', | |
69 | sr.Unit.METER_SECOND: 'm/s', | |
70 | sr.Unit.HECTOPASCAL: 'hPa', | |
71 | sr.Unit.HUMIDITY_293K: '%rF', | |
72 | sr.Unit.DEGREE: u'\u00B0', | |
73 | sr.Unit.HENRY: 'H' | |
74 | } | |
75 | ||
76 | return units.get(u, '') | |
77 | ||
78 | def format_mqflags(self, mqflags): | |
79 | if sr.QuantityFlag.AC in mqflags: | |
80 | return 'AC' | |
81 | elif sr.QuantityFlag.DC in mqflags: | |
82 | return 'DC' | |
83 | else: | |
84 | return '' | |
85 | ||
86 | def format_value(self, mag): | |
87 | if mag == self.inf: | |
88 | return u'\u221E' | |
89 | return '{:f}'.format(mag) | |
90 | ||
91 | def getItem(self, device, channel): | |
92 | '''Returns the item for the device + channel combination from the model, | |
93 | or creates a new item if no existing one matches.''' | |
94 | ||
95 | # unique identifier for the device + channel | |
96 | # TODO: isn't there something better? | |
97 | uid = ( | |
98 | device.vendor, | |
99 | device.model, | |
100 | device.serial_number(), | |
101 | device.connection_id(), | |
102 | channel.index | |
103 | ) | |
104 | ||
105 | # find the correct item in the model | |
106 | for row in range(self.rowCount()): | |
107 | item = self.item(row) | |
108 | rid = item.data(MeasurementDataModel._idRole) | |
109 | rid = tuple(rid) # PySide returns a list | |
110 | if uid == rid: | |
111 | return item | |
112 | ||
113 | # nothing found, create a new item | |
114 | desc = '{} {}, channel "{}"'.format( | |
115 | device.vendor, device.model, channel.name) | |
116 | ||
117 | item = QtGui.QStandardItem() | |
118 | item.setData(uid, MeasurementDataModel._idRole) | |
119 | item.setData(desc, MeasurementDataModel.descRole) | |
120 | self.appendRow(item) | |
121 | self.sort(0) | |
122 | return item | |
123 | ||
124 | @QtCore.Slot(object, object, object) | |
125 | def update(self, device, channel, data): | |
126 | '''Updates the data for the device (+channel) with the most recent | |
127 | measurement from the given payload.''' | |
128 | ||
129 | item = self.getItem(device, channel) | |
130 | ||
131 | value, unit, mqflags = data | |
132 | value_str = self.format_value(value) | |
133 | unit_str = self.format_unit(unit) | |
134 | mqflags_str = self.format_mqflags(mqflags) | |
135 | ||
136 | disp = ' '.join([value_str, unit_str, mqflags_str]) | |
137 | item.setData(disp, QtCore.Qt.DisplayRole) | |
138 | ||
139 | class MultimeterDelegate(QtGui.QStyledItemDelegate): | |
140 | '''Delegate to show the data items from a MeasurementDataModel.''' | |
141 | ||
142 | def __init__(self, parent, font): | |
143 | '''Initializes the delegate. | |
144 | ||
145 | :param font: Font used for the description text, the value is drawn | |
146 | with a slightly bigger and bold variant of the font. | |
147 | ''' | |
148 | ||
149 | super(self.__class__, self).__init__(parent) | |
150 | ||
151 | self._nfont = font | |
152 | self._bfont = QtGui.QFont(self._nfont) | |
153 | ||
154 | self._bfont.setBold(True) | |
155 | if self._bfont.pixelSize() != -1: | |
156 | self._bfont.setPixelSize(self._bfont.pixelSize() * 1.8) | |
157 | else: | |
158 | self._bfont.setPointSizeF(self._bfont.pointSizeF() * 1.8) | |
159 | ||
160 | fi = QtGui.QFontInfo(self._nfont) | |
161 | self._nfontheight = fi.pixelSize() | |
162 | ||
163 | fm = QtGui.QFontMetrics(self._bfont) | |
164 | r = fm.boundingRect('-XX.XXXXXX X XX') | |
165 | self._size = QtCore.QSize(r.width() * 1.2, r.height() * 3.5) | |
166 | ||
167 | def sizeHint(self, option=None, index=None): | |
168 | return self._size | |
169 | ||
170 | def paint(self, painter, options, index): | |
171 | value = index.data(QtCore.Qt.DisplayRole) | |
172 | desc = index.data(MeasurementDataModel.descRole) | |
173 | ||
174 | # description in the top left corner | |
175 | painter.setFont(self._nfont) | |
176 | p = options.rect.topLeft() | |
177 | p += QtCore.QPoint(self._nfontheight, 2 * self._nfontheight) | |
178 | painter.drawText(p, desc) | |
179 | ||
180 | # value in the center | |
181 | painter.setFont(self._bfont) | |
182 | r = options.rect.adjusted(self._nfontheight, 2.5 * self._nfontheight, | |
183 | 0, 0) | |
184 | painter.drawText(r, QtCore.Qt.AlignCenter, value) |