2 * This file is part of the PulseView project.
4 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
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.
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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
27 #include <QApplication>
30 #include <QFormLayout>
31 #include <QGridLayout>
36 #include "analogsignal.hpp"
37 #include "pv/data/analog.hpp"
38 #include "pv/data/analogsegment.hpp"
39 #include "pv/data/signalbase.hpp"
40 #include "pv/view/view.hpp"
41 #include "pv/globalsettings.hpp"
43 #include <libsigrokcxx/libsigrokcxx.hpp>
48 using std::shared_ptr
;
55 const QColor
AnalogSignal::SignalColours
[4] = {
56 QColor(0xC4, 0xA0, 0x00), // Yellow
57 QColor(0x87, 0x20, 0x7A), // Magenta
58 QColor(0x20, 0x4A, 0x87), // Blue
59 QColor(0x4E, 0x9A, 0x06) // Green
62 const QColor
AnalogSignal::GridMajorColor
= QColor(0, 0, 0, 40*256/100);
63 const QColor
AnalogSignal::GridMinorColor
= QColor(0, 0, 0, 20*256/100);
65 const QColor
AnalogSignal::SamplingPointColour(0x77, 0x77, 0x77);
67 const float AnalogSignal::EnvelopeThreshold
= 256.0f
;
69 const int AnalogSignal::MaximumVDivs
= 10;
70 const int AnalogSignal::MinScaleIndex
= -6;
71 const int AnalogSignal::MaxScaleIndex
= 7;
73 const int AnalogSignal::InfoTextMarginRight
= 20;
74 const int AnalogSignal::InfoTextMarginBottom
= 5;
76 AnalogSignal::AnalogSignal(
78 shared_ptr
<data::SignalBase
> base
) :
79 Signal(session
, base
),
80 scale_index_(4), // 20 per div
81 scale_index_drag_offset_(0),
82 div_height_(3 * QFontMetrics(QApplication::font()).height()),
88 pv::data::Analog
* analog_data
=
89 dynamic_cast<pv::data::Analog
*>(data().get());
91 connect(analog_data
, SIGNAL(samples_added(QObject
*, uint64_t, uint64_t)),
92 this, SLOT(on_samples_added()));
94 base_
->set_colour(SignalColours
[base_
->index() % countof(SignalColours
)]);
98 shared_ptr
<pv::data::SignalData
> AnalogSignal::data() const
100 return base_
->analog_data();
103 void AnalogSignal::save_settings(QSettings
&settings
) const
105 settings
.setValue("pos_vdivs", pos_vdivs_
);
106 settings
.setValue("neg_vdivs", neg_vdivs_
);
107 settings
.setValue("scale_index", scale_index_
);
108 settings
.setValue("autoranging", autoranging_
);
111 void AnalogSignal::restore_settings(QSettings
&settings
)
113 if (settings
.contains("pos_vdivs"))
114 pos_vdivs_
= settings
.value("pos_vdivs").toInt();
116 if (settings
.contains("neg_vdivs"))
117 neg_vdivs_
= settings
.value("neg_vdivs").toInt();
119 if (settings
.contains("scale_index")) {
120 scale_index_
= settings
.value("scale_index").toInt();
124 if (settings
.contains("autoranging"))
125 autoranging_
= settings
.value("autoranging").toBool();
128 std::pair
<int, int> AnalogSignal::v_extents() const
130 const int ph
= pos_vdivs_
* div_height_
;
131 const int nh
= neg_vdivs_
* div_height_
;
132 return make_pair(-ph
, nh
);
135 int AnalogSignal::scale_handle_offset() const
137 const int h
= (pos_vdivs_
+ neg_vdivs_
) * div_height_
;
139 return ((scale_index_drag_offset_
- scale_index_
) *
143 void AnalogSignal::scale_handle_dragged(int offset
)
145 const int h
= (pos_vdivs_
+ neg_vdivs_
) * div_height_
;
147 scale_index_
= scale_index_drag_offset_
-
148 (offset
+ h
/ 2) / (h
/ 4);
153 void AnalogSignal::scale_handle_drag_release()
155 scale_index_drag_offset_
= scale_index_
;
159 void AnalogSignal::paint_back(QPainter
&p
, const ViewItemPaintParams
&pp
)
161 if (base_
->enabled()) {
162 Trace::paint_back(p
, pp
);
163 paint_axis(p
, pp
, get_visual_y());
167 void AnalogSignal::paint_mid(QPainter
&p
, const ViewItemPaintParams
&pp
)
169 assert(base_
->analog_data());
172 const int y
= get_visual_y();
174 if (!base_
->enabled())
177 paint_grid(p
, y
, pp
.left(), pp
.right());
179 const deque
< shared_ptr
<pv::data::AnalogSegment
> > &segments
=
180 base_
->analog_data()->analog_segments();
181 if (segments
.empty())
184 const shared_ptr
<pv::data::AnalogSegment
> &segment
=
187 const double pixels_offset
= pp
.pixels_offset();
188 const double samplerate
= max(1.0, segment
->samplerate());
189 const pv::util::Timestamp
& start_time
= segment
->start_time();
190 const int64_t last_sample
= segment
->get_sample_count() - 1;
191 const double samples_per_pixel
= samplerate
* pp
.scale();
192 const pv::util::Timestamp start
= samplerate
* (pp
.offset() - start_time
);
193 const pv::util::Timestamp end
= start
+ samples_per_pixel
* pp
.width();
195 const int64_t start_sample
= min(max(floor(start
).convert_to
<int64_t>(),
196 (int64_t)0), last_sample
);
197 const int64_t end_sample
= min(max((ceil(end
) + 1).convert_to
<int64_t>(),
198 (int64_t)0), last_sample
);
200 if (samples_per_pixel
< EnvelopeThreshold
)
201 paint_trace(p
, segment
, y
, pp
.left(),
202 start_sample
, end_sample
,
203 pixels_offset
, samples_per_pixel
);
205 paint_envelope(p
, segment
, y
, pp
.left(),
206 start_sample
, end_sample
,
207 pixels_offset
, samples_per_pixel
);
210 void AnalogSignal::paint_fore(QPainter
&p
, const ViewItemPaintParams
&pp
)
215 const int y
= get_visual_y();
217 // Show the info section on the right side of the trace
218 const QString infotext
= QString("%1 V/div").arg(resolution_
);
220 p
.setPen(base_
->colour());
221 p
.setFont(QApplication::font());
223 const QRectF bounding_rect
= QRectF(pp
.left(),
224 y
+ v_extents().first
,
225 pp
.width() - InfoTextMarginRight
,
226 v_extents().second
- v_extents().first
- InfoTextMarginBottom
);
228 p
.drawText(bounding_rect
, Qt::AlignRight
| Qt::AlignBottom
, infotext
);
231 void AnalogSignal::paint_grid(QPainter
&p
, int y
, int left
, int right
)
233 p
.setRenderHint(QPainter::Antialiasing
, false);
235 if (pos_vdivs_
> 0) {
236 p
.setPen(QPen(GridMajorColor
, 1, Qt::DashLine
));
237 for (int i
= 1; i
<= pos_vdivs_
; i
++) {
238 const float dy
= i
* div_height_
;
239 p
.drawLine(QLineF(left
, y
- dy
, right
, y
- dy
));
242 p
.setPen(QPen(GridMinorColor
, 1, Qt::DashLine
));
243 for (int i
= 0; i
< pos_vdivs_
; i
++) {
244 const float dy
= i
* div_height_
;
245 const float dy25
= dy
+ (0.25 * div_height_
);
246 const float dy50
= dy
+ (0.50 * div_height_
);
247 const float dy75
= dy
+ (0.75 * div_height_
);
248 p
.drawLine(QLineF(left
, y
- dy25
, right
, y
- dy25
));
249 p
.drawLine(QLineF(left
, y
- dy50
, right
, y
- dy50
));
250 p
.drawLine(QLineF(left
, y
- dy75
, right
, y
- dy75
));
254 if (neg_vdivs_
> 0) {
255 p
.setPen(QPen(GridMajorColor
, 1, Qt::DashLine
));
256 for (int i
= 1; i
<= neg_vdivs_
; i
++) {
257 const float dy
= i
* div_height_
;
258 p
.drawLine(QLineF(left
, y
+ dy
, right
, y
+ dy
));
261 p
.setPen(QPen(GridMinorColor
, 1, Qt::DashLine
));
262 for (int i
= 0; i
< neg_vdivs_
; i
++) {
263 const float dy
= i
* div_height_
;
264 const float dy25
= dy
+ (0.25 * div_height_
);
265 const float dy50
= dy
+ (0.50 * div_height_
);
266 const float dy75
= dy
+ (0.75 * div_height_
);
267 p
.drawLine(QLineF(left
, y
+ dy25
, right
, y
+ dy25
));
268 p
.drawLine(QLineF(left
, y
+ dy50
, right
, y
+ dy50
));
269 p
.drawLine(QLineF(left
, y
+ dy75
, right
, y
+ dy75
));
273 p
.setRenderHint(QPainter::Antialiasing
, true);
276 void AnalogSignal::paint_trace(QPainter
&p
,
277 const shared_ptr
<pv::data::AnalogSegment
> &segment
,
278 int y
, int left
, const int64_t start
, const int64_t end
,
279 const double pixels_offset
, const double samples_per_pixel
)
281 p
.setPen(base_
->colour());
283 const int64_t points_count
= end
- start
;
285 QPointF
*points
= new QPointF
[points_count
];
286 QPointF
*point
= points
;
288 QRectF
*const sampling_points
= new QRectF
[points_count
];
289 QRectF
*sampling_point
= sampling_points
;
291 pv::data::SegmentAnalogDataIterator
* it
=
292 segment
->begin_sample_iteration(start
);
295 for (int64_t sample
= start
; sample
!= end
; sample
++) {
296 const float x
= (sample
/ samples_per_pixel
-
297 pixels_offset
) + left
;
299 *point
++ = QPointF(x
, y
- *((float*)it
->value
) * scale_
);
300 *sampling_point
++ = QRectF(x
- (w
/ 2), y
- *((float*)it
->value
) * scale_
- (w
/ 2), w
, w
);
302 segment
->continue_sample_iteration(it
, 1);
304 segment
->end_sample_iteration(it
);
306 p
.drawPolyline(points
, points_count
);
308 // Paint the sampling points if enabled
309 GlobalSettings settings
;
310 const bool show_sampling_points
=
311 settings
.value(GlobalSettings::Key_View_ShowSamplingPoints
).toBool();
312 if (show_sampling_points
) {
313 p
.setPen(SamplingPointColour
);
314 p
.drawRects(sampling_points
, points_count
);
318 delete[] sampling_points
;
321 void AnalogSignal::paint_envelope(QPainter
&p
,
322 const shared_ptr
<pv::data::AnalogSegment
> &segment
,
323 int y
, int left
, const int64_t start
, const int64_t end
,
324 const double pixels_offset
, const double samples_per_pixel
)
326 using pv::data::AnalogSegment
;
328 AnalogSegment::EnvelopeSection e
;
329 segment
->get_envelope_section(e
, start
, end
, samples_per_pixel
);
334 p
.setPen(QPen(Qt::NoPen
));
335 p
.setBrush(base_
->colour());
337 QRectF
*const rects
= new QRectF
[e
.length
];
338 QRectF
*rect
= rects
;
340 for (uint64_t sample
= 0; sample
< e
.length
-1; sample
++) {
341 const float x
= ((e
.scale
* sample
+ e
.start
) /
342 samples_per_pixel
- pixels_offset
) + left
;
343 const AnalogSegment::EnvelopeSample
*const s
=
346 // We overlap this sample with the next so that vertical
347 // gaps do not appear during steep rising or falling edges
348 const float b
= y
- max(s
->max
, (s
+1)->min
) * scale_
;
349 const float t
= y
- min(s
->min
, (s
+1)->max
) * scale_
;
352 if (h
>= 0.0f
&& h
<= 1.0f
)
354 if (h
<= 0.0f
&& h
>= -1.0f
)
357 *rect
++ = QRectF(x
, t
, 1.0f
, h
);
360 p
.drawRects(rects
, e
.length
);
366 float AnalogSignal::get_resolution(int scale_index
)
368 const float seq
[] = {1.0f
, 2.0f
, 5.0f
};
370 const int offset
= std::numeric_limits
<int>::max() / (2 * countof(seq
));
371 const std::div_t d
= std::div(
372 (int)(scale_index
+ countof(seq
) * offset
),
375 return powf(10.0f
, d
.quot
- offset
) * seq
[d
.rem
];
378 void AnalogSignal::update_scale()
380 resolution_
= get_resolution(scale_index_
);
381 scale_
= div_height_
/ resolution_
;
384 void AnalogSignal::perform_autoranging(bool force_update
)
386 const deque
< shared_ptr
<pv::data::AnalogSegment
> > &segments
=
387 base_
->analog_data()->analog_segments();
389 if (segments
.empty())
392 static double prev_min
= 0, prev_max
= 0;
393 double min
= 0, max
= 0;
395 for (shared_ptr
<pv::data::AnalogSegment
> segment
: segments
) {
396 std::pair
<double, double> mm
= segment
->get_min_max();
397 min
= std::min(min
, mm
.first
);
398 max
= std::max(max
, mm
.second
);
401 if ((min
== prev_min
) && (max
== prev_max
) && !force_update
)
407 // Use all divs for the positive range if there are no negative values
408 if ((min
== 0) && (neg_vdivs_
> 0)) {
409 pos_vdivs_
+= neg_vdivs_
;
413 // Split up the divs if there are negative values but no negative divs
414 if ((min
< 0) && (neg_vdivs_
== 0)) {
415 neg_vdivs_
= pos_vdivs_
/ 2;
416 pos_vdivs_
-= neg_vdivs_
;
419 double min_value_per_div
;
420 if ((pos_vdivs_
> 0) && (neg_vdivs_
> 0))
421 min_value_per_div
= std::max(max
/ pos_vdivs_
, -min
/ neg_vdivs_
);
422 else if (pos_vdivs_
> 0)
423 min_value_per_div
= max
/ pos_vdivs_
;
425 min_value_per_div
= -min
/ neg_vdivs_
;
427 // Find first scale value that is bigger than the value we need
428 for (int i
= MinScaleIndex
; i
< MaxScaleIndex
; i
++)
429 if (get_resolution(i
) > min_value_per_div
) {
437 void AnalogSignal::populate_popup_form(QWidget
*parent
, QFormLayout
*form
)
439 // Add the standard options
440 Signal::populate_popup_form(parent
, form
);
442 QFormLayout
*const layout
= new QFormLayout
;
444 // Add the number of vdivs
445 QSpinBox
*pvdiv_sb
= new QSpinBox(parent
);
446 pvdiv_sb
->setRange(0, MaximumVDivs
);
447 pvdiv_sb
->setValue(pos_vdivs_
);
448 connect(pvdiv_sb
, SIGNAL(valueChanged(int)),
449 this, SLOT(on_pos_vdivs_changed(int)));
450 layout
->addRow(tr("Number of pos vertical divs"), pvdiv_sb
);
452 QSpinBox
*nvdiv_sb
= new QSpinBox(parent
);
453 nvdiv_sb
->setRange(0, MaximumVDivs
);
454 nvdiv_sb
->setValue(neg_vdivs_
);
455 connect(nvdiv_sb
, SIGNAL(valueChanged(int)),
456 this, SLOT(on_neg_vdivs_changed(int)));
457 layout
->addRow(tr("Number of neg vertical divs"), nvdiv_sb
);
459 // Add the vertical resolution
460 resolution_cb_
= new QComboBox(parent
);
462 for (int i
= MinScaleIndex
; i
< MaxScaleIndex
; i
++) {
463 const QString label
= QString("%1").arg(get_resolution(i
));
464 resolution_cb_
->insertItem(0, label
, QVariant(i
));
467 const int cur_idx
= resolution_cb_
->findData(QVariant(scale_index_
));
468 resolution_cb_
->setCurrentIndex(cur_idx
);
470 connect(resolution_cb_
, SIGNAL(currentIndexChanged(int)),
471 this, SLOT(on_resolution_changed(int)));
473 QGridLayout
*const vdiv_layout
= new QGridLayout
;
474 QLabel
*const vdiv_unit
= new QLabel(tr("V/div"));
475 vdiv_layout
->addWidget(resolution_cb_
, 0, 0);
476 vdiv_layout
->addWidget(vdiv_unit
, 0, 1);
478 layout
->addRow(tr("Vertical resolution"), vdiv_layout
);
480 // Add the autoranging checkbox
481 QCheckBox
* autoranging_cb
= new QCheckBox();
482 autoranging_cb
->setCheckState(autoranging_
? Qt::Checked
: Qt::Unchecked
);
484 connect(autoranging_cb
, SIGNAL(stateChanged(int)),
485 this, SLOT(on_autoranging_changed(int)));
487 layout
->addRow(tr("Autoranging"), autoranging_cb
);
489 form
->addRow(layout
);
492 void AnalogSignal::on_samples_added()
494 perform_autoranging();
497 // Call order is important, otherwise the lazy event handler won't work
498 owner_
->extents_changed(false, true);
499 owner_
->row_item_appearance_changed(false, true);
503 void AnalogSignal::on_pos_vdivs_changed(int vdivs
)
508 perform_autoranging(true);
511 // Call order is important, otherwise the lazy event handler won't work
512 owner_
->extents_changed(false, true);
513 owner_
->row_item_appearance_changed(false, true);
517 void AnalogSignal::on_neg_vdivs_changed(int vdivs
)
522 perform_autoranging(true);
525 // Call order is important, otherwise the lazy event handler won't work
526 owner_
->extents_changed(false, true);
527 owner_
->row_item_appearance_changed(false, true);
531 void AnalogSignal::on_resolution_changed(int index
)
533 scale_index_
= resolution_cb_
->itemData(index
).toInt();
537 owner_
->row_item_appearance_changed(false, true);
540 void AnalogSignal::on_autoranging_changed(int state
)
542 autoranging_
= (state
== Qt::Checked
);
545 perform_autoranging(true);
548 // Call order is important, otherwise the lazy event handler won't work
549 owner_
->extents_changed(false, true);
550 owner_
->row_item_appearance_changed(false, true);
554 } // namespace TraceView