]>
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 | ||
02862990 JS |
136 | # The display role is a tuple containing the value and the unit/flags. |
137 | disp = (value_str, ' '.join([unit_str, mqflags_str])) | |
48723bbb JS |
138 | item.setData(disp, QtCore.Qt.DisplayRole) |
139 | ||
140 | class MultimeterDelegate(QtGui.QStyledItemDelegate): | |
141 | '''Delegate to show the data items from a MeasurementDataModel.''' | |
142 | ||
143 | def __init__(self, parent, font): | |
144 | '''Initializes the delegate. | |
145 | ||
146 | :param font: Font used for the description text, the value is drawn | |
147 | with a slightly bigger and bold variant of the font. | |
148 | ''' | |
149 | ||
150 | super(self.__class__, self).__init__(parent) | |
151 | ||
152 | self._nfont = font | |
153 | self._bfont = QtGui.QFont(self._nfont) | |
154 | ||
155 | self._bfont.setBold(True) | |
156 | if self._bfont.pixelSize() != -1: | |
157 | self._bfont.setPixelSize(self._bfont.pixelSize() * 1.8) | |
158 | else: | |
159 | self._bfont.setPointSizeF(self._bfont.pointSizeF() * 1.8) | |
160 | ||
161 | fi = QtGui.QFontInfo(self._nfont) | |
162 | self._nfontheight = fi.pixelSize() | |
163 | ||
164 | fm = QtGui.QFontMetrics(self._bfont) | |
165 | r = fm.boundingRect('-XX.XXXXXX X XX') | |
02862990 JS |
166 | self._size = QtCore.QSize(r.width() * 1.4, r.height() * 3.5) |
167 | ||
168 | # Values used to calculate the positions of the strings in the | |
169 | # 'paint()' function. | |
170 | self._space_width = fm.boundingRect('_').width() | |
171 | self._value_width = fm.boundingRect('-XX.XXXXXX').width() | |
48723bbb JS |
172 | |
173 | def sizeHint(self, option=None, index=None): | |
174 | return self._size | |
175 | ||
176 | def paint(self, painter, options, index): | |
02862990 | 177 | value, unit = index.data(QtCore.Qt.DisplayRole) |
48723bbb JS |
178 | desc = index.data(MeasurementDataModel.descRole) |
179 | ||
180 | # description in the top left corner | |
181 | painter.setFont(self._nfont) | |
182 | p = options.rect.topLeft() | |
183 | p += QtCore.QPoint(self._nfontheight, 2 * self._nfontheight) | |
184 | painter.drawText(p, desc) | |
185 | ||
48723bbb | 186 | painter.setFont(self._bfont) |
02862990 JS |
187 | |
188 | # value about in the center | |
189 | p = options.rect.center() | |
190 | p += QtCore.QPoint(-3 * self._space_width, self._nfontheight) | |
191 | rect = QtCore.QRect(0, 0, self._value_width, 2 * self._nfontheight) | |
192 | rect.moveCenter(p) | |
193 | painter.drawText(rect, QtCore.Qt.AlignRight, value) | |
194 | ||
195 | # unit right of the value | |
196 | rect.moveLeft(rect.right()) | |
197 | rect.adjust(self._space_width, 0, 0, 0) | |
198 | painter.drawText(rect, QtCore.Qt.AlignLeft, unit) |