X-Git-Url: https://sigrok.org/gitweb/?p=pulseview.git;a=blobdiff_plain;f=pv%2Fviews%2Ftrace%2Fanalogsignal.cpp;h=ac5e83c834880f4e85f5f9fd2c8203ebf017301b;hp=58a186ecf0229cd28e7a2bd134f9e76613205667;hb=82e7f4b3f640b6f8bfbd094e478cbe344cc8fc11;hpb=1d150cd6ea6d9c465d607377bba435a065f104cd diff --git a/pv/views/trace/analogsignal.cpp b/pv/views/trace/analogsignal.cpp index 58a186ec..ac5e83c8 100644 --- a/pv/views/trace/analogsignal.cpp +++ b/pv/views/trace/analogsignal.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include "logicsignal.hpp" #include "view.hpp" +#include "pv/util.hpp" #include "pv/data/analog.hpp" #include "pv/data/analogsegment.hpp" #include "pv/data/logic.hpp" @@ -49,21 +51,25 @@ using std::deque; using std::div; using std::div_t; +// Note that "using std::isnan;" is _not_ put here since that would break +// compilation on some platforms. Use "std::isnan()" instead in checks below. using std::max; using std::make_pair; using std::min; using std::numeric_limits; +using std::out_of_range; using std::pair; using std::shared_ptr; using std::vector; using pv::data::SignalBase; +using pv::util::SIPrefix; namespace pv { namespace views { namespace trace { -const QColor AnalogSignal::SignalColours[4] = { +const QColor AnalogSignal::SignalColors[4] = { QColor(0xC4, 0xA0, 0x00), // Yellow QColor(0x87, 0x20, 0x7A), // Magenta QColor(0x20, 0x4A, 0x87), // Blue @@ -74,11 +80,14 @@ const QPen AnalogSignal::AxisPen(QColor(0, 0, 0, 30 * 256 / 100), 2); const QColor AnalogSignal::GridMajorColor = QColor(0, 0, 0, 40 * 256 / 100); const QColor AnalogSignal::GridMinorColor = QColor(0, 0, 0, 20 * 256 / 100); -const QColor AnalogSignal::SamplingPointColour(0x77, 0x77, 0x77); +const QColor AnalogSignal::SamplingPointColor(0x77, 0x77, 0x77); +const QColor AnalogSignal::SamplingPointColorLo = QColor(200, 0, 0, 80 * 256 / 100); +const QColor AnalogSignal::SamplingPointColorNe = QColor(0, 0, 0, 80 * 256 / 100); +const QColor AnalogSignal::SamplingPointColorHi = QColor(0, 200, 0, 80 * 256 / 100); const QColor AnalogSignal::ThresholdColor = QColor(0, 0, 0, 30 * 256 / 100); const QColor AnalogSignal::ThresholdColorLo = QColor(255, 0, 0, 8 * 256 / 100); -const QColor AnalogSignal::ThresholdColorNe = QColor(0, 0, 0, 6 * 256 / 100); +const QColor AnalogSignal::ThresholdColorNe = QColor(0, 0, 0, 10 * 256 / 100); const QColor AnalogSignal::ThresholdColorHi = QColor(0, 255, 0, 8 * 256 / 100); const int64_t AnalogSignal::TracePaintBlockSize = 1024 * 1024; // 4 MiB (due to float) @@ -96,12 +105,12 @@ AnalogSignal::AnalogSignal( shared_ptr base) : Signal(session, base), scale_index_(4), // 20 per div - scale_index_drag_offset_(0), pos_vdivs_(1), neg_vdivs_(1), resolution_(0), display_type_(DisplayBoth), - autoranging_(true) + autoranging_(true), + value_at_hover_pos_(std::numeric_limits::quiet_NaN()) { axis_pen_ = AxisPen; @@ -112,9 +121,12 @@ AnalogSignal::AnalogSignal( this, SLOT(on_min_max_changed(float, float))); GlobalSettings gs; + conversion_threshold_disp_mode_ = + gs.value(GlobalSettings::Key_View_ConversionThresholdDispMode).toInt(); + div_height_ = gs.value(GlobalSettings::Key_View_DefaultDivHeight).toInt(); - base_->set_colour(SignalColours[base_->index() % countof(SignalColours)]); + base_->set_color(SignalColors[base_->index() % countof(SignalColors)]); update_scale(); } @@ -171,26 +183,12 @@ pair AnalogSignal::v_extents() const return make_pair(-ph, nh); } -int AnalogSignal::scale_handle_offset() const -{ - const int h = (pos_vdivs_ + neg_vdivs_) * div_height_; - - return ((scale_index_drag_offset_ - scale_index_) * h / 4) - h / 2; -} - -void AnalogSignal::scale_handle_dragged(int offset) +void AnalogSignal::on_setting_changed(const QString &key, const QVariant &value) { - const int h = (pos_vdivs_ + neg_vdivs_) * div_height_; + Signal::on_setting_changed(key, value); - scale_index_ = scale_index_drag_offset_ - (offset + h / 2) / (h / 4); - - update_scale(); -} - -void AnalogSignal::scale_handle_drag_release() -{ - scale_index_drag_offset_ = scale_index_; - update_scale(); + if (key == GlobalSettings::Key_View_ConversionThresholdDispMode) + on_settingViewConversionThresholdDispMode_changed(value); } void AnalogSignal::paint_back(QPainter &p, ViewItemPaintParams &pp) @@ -198,15 +196,13 @@ void AnalogSignal::paint_back(QPainter &p, ViewItemPaintParams &pp) if (!base_->enabled()) return; - // TODO Register a change handler instead of querying this with every repaint - GlobalSettings settings; - const bool show_conversion_thresholds = - settings.value(GlobalSettings::Key_View_ShowConversionThresholds).toBool(); + bool paint_thr_bg = + conversion_threshold_disp_mode_ == GlobalSettings::ConvThrDispMode_Background; const vector thresholds = base_->get_conversion_thresholds(); // Only display thresholds if we have some and we show analog samples - if ((thresholds.size() > 0) && show_conversion_thresholds && + if ((thresholds.size() > 0) && paint_thr_bg && ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth))) { const int visual_y = get_visual_y(); @@ -216,8 +212,10 @@ void AnalogSignal::paint_back(QPainter &p, ViewItemPaintParams &pp) // Draw high/neutral/low areas if (thresholds.size() == 2) { - const double thr_lo = visual_y - thresholds[0] * scale_; - const double thr_hi = visual_y - thresholds[1] * scale_; + int thr_lo = visual_y - thresholds[0] * scale_; + int thr_hi = visual_y - thresholds[1] * scale_; + thr_lo = min(max(thr_lo, top), btm); + thr_hi = min(max(thr_hi, top), btm); p.fillRect(QRectF(pp.left(), top, pp.width(), thr_hi - top), QBrush(ThresholdColorHi)); @@ -226,7 +224,8 @@ void AnalogSignal::paint_back(QPainter &p, ViewItemPaintParams &pp) p.fillRect(QRectF(pp.left(), thr_lo, pp.width(), btm - thr_lo), QBrush(ThresholdColorLo)); } else { - const double thr = visual_y - thresholds[0] * scale_; + int thr = visual_y - thresholds[0] * scale_; + thr = min(max(thr, top), btm); p.fillRect(QRectF(pp.left(), top, pp.width(), thr - top), QBrush(ThresholdColorHi)); @@ -254,18 +253,14 @@ void AnalogSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp) if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) { paint_grid(p, y, pp.left(), pp.right()); - const deque< shared_ptr > &segments = - base_->analog_data()->analog_segments(); - if (segments.empty()) + shared_ptr segment = get_analog_segment_to_paint(); + if (!segment || (segment->get_sample_count() == 0)) return; - const shared_ptr &segment = - segments.front(); - const double pixels_offset = pp.pixels_offset(); const double samplerate = max(1.0, segment->samplerate()); const pv::util::Timestamp& start_time = segment->start_time(); - const int64_t last_sample = segment->get_sample_count() - 1; + const int64_t last_sample = (int64_t)segment->get_sample_count() - 1; const double samples_per_pixel = samplerate * pp.scale(); const pv::util::Timestamp start = samplerate * (pp.offset() - start_time); const pv::util::Timestamp end = start + samples_per_pixel * pp.width(); @@ -276,12 +271,10 @@ void AnalogSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp) (int64_t)0), last_sample); if (samples_per_pixel < EnvelopeThreshold) - paint_trace(p, segment, y, pp.left(), - start_sample, end_sample, + paint_trace(p, segment, y, pp.left(), start_sample, end_sample, pixels_offset, samples_per_pixel); else - paint_envelope(p, segment, y, pp.left(), - start_sample, end_sample, + paint_envelope(p, segment, y, pp.left(), start_sample, end_sample, pixels_offset, samples_per_pixel); } @@ -297,10 +290,19 @@ void AnalogSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp) if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) { const int y = get_visual_y(); - // Show the info section on the right side of the trace - const QString infotext = QString("%1 V/div").arg(resolution_); + QString infotext; - p.setPen(base_->colour()); + // Show the info section on the right side of the trace, including + // the value at the hover point when the hover marker is enabled + // and we have corresponding data available + if (show_hover_marker_ && !std::isnan(value_at_hover_pos_)) { + infotext = QString("[%1] %2 V/div") + .arg(format_value_si(value_at_hover_pos_, SIPrefix::unspecified, 0, "V", false)) + .arg(resolution_); + } else + infotext = QString("%1 V/div").arg(resolution_); + + p.setPen(base_->color()); p.setFont(QApplication::font()); const QRectF bounding_rect = QRectF(pp.left(), @@ -310,6 +312,9 @@ void AnalogSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp) p.drawText(bounding_rect, Qt::AlignRight | Qt::AlignBottom, infotext); } + + if (show_hover_marker_) + paint_hover_marker(p); } void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right) @@ -373,55 +378,90 @@ void AnalogSignal::paint_trace(QPainter &p, if (end <= start) return; + bool paint_thr_dots = + (base_->get_conversion_type() != data::SignalBase::NoConversion) && + (conversion_threshold_disp_mode_ == GlobalSettings::ConvThrDispMode_Dots); + + vector thresholds; + if (paint_thr_dots) + thresholds = base_->get_conversion_thresholds(); + // Calculate and paint the sampling points if enabled and useful GlobalSettings settings; const bool show_sampling_points = - settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() && - (samples_per_pixel < 0.25); + (settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() || + paint_thr_dots) && (samples_per_pixel < 0.25); - p.setPen(base_->colour()); + p.setPen(base_->color()); - const int64_t points_count = end - start; + const int64_t points_count = end - start + 1; QPointF *points = new QPointF[points_count]; QPointF *point = points; - QRectF *sampling_points = nullptr; - if (show_sampling_points) - sampling_points = new QRectF[points_count]; - QRectF *sampling_point = sampling_points; + vector sampling_points[3]; int64_t sample_count = min(points_count, TracePaintBlockSize); int64_t block_sample = 0; float *sample_block = new float[TracePaintBlockSize]; segment->get_samples(start, start + sample_count, sample_block); + if (show_hover_marker_) + reset_pixel_values(); + const int w = 2; - for (int64_t sample = start; sample != end; sample++, block_sample++) { + for (int64_t sample = start; sample <= end; sample++, block_sample++) { + // Fetch next block of samples if we finished the current one if (block_sample == TracePaintBlockSize) { block_sample = 0; sample_count = min(points_count - sample, TracePaintBlockSize); segment->get_samples(sample, sample + sample_count, sample_block); } - const float x = (sample / samples_per_pixel - - pixels_offset) + left; + const float abs_x = sample / samples_per_pixel - pixels_offset; + const float x = left + abs_x; *point++ = QPointF(x, y - sample_block[block_sample] * scale_); - if (show_sampling_points) - *sampling_point++ = - QRectF(x - (w / 2), y - sample_block[block_sample] * scale_ - (w / 2), w, w); + // Generate the pixel<->value lookup table for the mouse hover + if (show_hover_marker_) + process_next_sample_value(abs_x, sample_block[block_sample]); + + // Create the sampling points if needed + if (show_sampling_points) { + int idx = 0; // Neutral + + if (paint_thr_dots) { + if (thresholds.size() == 1) + idx = (sample_block[block_sample] >= thresholds[0]) ? 2 : 1; + else if (thresholds.size() == 2) { + if (sample_block[block_sample] > thresholds[1]) + idx = 2; // High + else if (sample_block[block_sample] < thresholds[0]) + idx = 1; // Low + } + } + + sampling_points[idx].emplace_back(x - (w / 2), y - sample_block[block_sample] * scale_ - (w / 2), w, w); + } } delete[] sample_block; p.drawPolyline(points, points_count); if (show_sampling_points) { - p.setPen(SamplingPointColour); - p.drawRects(sampling_points, points_count); - delete[] sampling_points; + if (paint_thr_dots) { + p.setPen(SamplingPointColorNe); + p.drawRects(sampling_points[0].data(), sampling_points[0].size()); + p.setPen(SamplingPointColorLo); + p.drawRects(sampling_points[1].data(), sampling_points[1].size()); + p.setPen(SamplingPointColorHi); + p.drawRects(sampling_points[2].data(), sampling_points[2].size()); + } else { + p.setPen(SamplingPointColor); + p.drawRects(sampling_points[0].data(), sampling_points[0].size()); + } } delete[] points; @@ -434,6 +474,10 @@ void AnalogSignal::paint_envelope(QPainter &p, { using pv::data::AnalogSegment; + // Note: Envelope painting currently doesn't generate a pixel<->value lookup table + if (show_hover_marker_) + reset_pixel_values(); + AnalogSegment::EnvelopeSection e; segment->get_envelope_section(e, start, end, samples_per_pixel); @@ -441,7 +485,7 @@ void AnalogSignal::paint_envelope(QPainter &p, return; p.setPen(QPen(Qt::NoPen)); - p.setBrush(base_->colour()); + p.setBrush(base_->color()); QRectF *const rects = new QRectF[e.length]; QRectF *rect = rects; @@ -449,8 +493,8 @@ void AnalogSignal::paint_envelope(QPainter &p, for (uint64_t sample = 0; sample < e.length - 1; sample++) { const float x = ((e.scale * sample + e.start) / samples_per_pixel - pixels_offset) + left; - const AnalogSegment::EnvelopeSample *const s = - e.samples + sample; + + const AnalogSegment::EnvelopeSample *const s = e.samples + sample; // We overlap this sample with the next so that vertical // gaps do not appear during steep rising or falling edges @@ -493,15 +537,10 @@ void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp) const float high_offset = y - ph + signal_margin + 0.5f; const float low_offset = y + nh - signal_margin - 0.5f; - const deque< shared_ptr > &segments = - base_->logic_data()->logic_segments(); - - if (segments.empty()) + shared_ptr segment = get_logic_segment_to_paint(); + if (!segment || (segment->get_sample_count() == 0)) return; - const shared_ptr &segment = - segments.front(); - double samplerate = segment->samplerate(); // Show sample rate as 1Hz when it is unknown @@ -510,7 +549,7 @@ void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp) const double pixels_offset = pp.pixels_offset(); const pv::util::Timestamp& start_time = segment->start_time(); - const int64_t last_sample = segment->get_sample_count() - 1; + const int64_t last_sample = (int64_t)segment->get_sample_count() - 1; const double samples_per_pixel = samplerate * pp.scale(); const double pixels_per_sample = 1 / samples_per_pixel; const pv::util::Timestamp start = samplerate * (pp.offset() - start_time); @@ -572,7 +611,7 @@ void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp) sampling_point_x += pixels_per_sample; }; - p.setPen(LogicSignal::EdgeColour); + p.setPen(LogicSignal::EdgeColor); p.drawLines(edge_lines, edge_count); delete[] edge_lines; @@ -580,10 +619,10 @@ void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp) const unsigned int max_cap_line_count = edges.size(); QLineF *const cap_lines = new QLineF[max_cap_line_count]; - p.setPen(LogicSignal::HighColour); + p.setPen(LogicSignal::HighColor); paint_logic_caps(p, cap_lines, edges, true, samples_per_pixel, pixels_offset, pp.left(), high_offset); - p.setPen(LogicSignal::LowColour); + p.setPen(LogicSignal::LowColor); paint_logic_caps(p, cap_lines, edges, false, samples_per_pixel, pixels_offset, pp.left(), low_offset); @@ -591,7 +630,7 @@ void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp) // Paint the sampling points if (show_sampling_points) { - p.setPen(SamplingPointColour); + p.setPen(SamplingPointColor); p.drawRects(sampling_points.data(), sampling_points.size()); } } @@ -615,6 +654,54 @@ void AnalogSignal::paint_logic_caps(QPainter &p, QLineF *const lines, p.drawLines(lines, line - lines); } +shared_ptr AnalogSignal::get_analog_segment_to_paint() const +{ + shared_ptr segment; + + const deque< shared_ptr > &segments = + base_->analog_data()->analog_segments(); + + if (!segments.empty()) { + if (segment_display_mode_ == ShowLastSegmentOnly) + segment = segments.back(); + + if ((segment_display_mode_ == ShowSingleSegmentOnly) || + (segment_display_mode_ == ShowLastCompleteSegmentOnly)) { + try { + segment = segments.at(current_segment_); + } catch (out_of_range&) { + qDebug() << "Current analog segment out of range for signal" << base_->name() << ":" << current_segment_; + } + } + } + + return segment; +} + +shared_ptr AnalogSignal::get_logic_segment_to_paint() const +{ + shared_ptr segment; + + const deque< shared_ptr > &segments = + base_->logic_data()->logic_segments(); + + if (!segments.empty()) { + if (segment_display_mode_ == ShowLastSegmentOnly) + segment = segments.back(); + + if ((segment_display_mode_ == ShowSingleSegmentOnly) || + (segment_display_mode_ == ShowLastCompleteSegmentOnly)) { + try { + segment = segments.at(current_segment_); + } catch (out_of_range&) { + qDebug() << "Current logic segment out of range for signal" << base_->name() << ":" << current_segment_; + } + } + } + + return segment; +} + float AnalogSignal::get_resolution(int scale_index) { const float seq[] = {1.0f, 2.0f, 5.0f}; @@ -746,6 +833,64 @@ void AnalogSignal::perform_autoranging(bool keep_divs, bool force_update) update_scale(); } +void AnalogSignal::reset_pixel_values() +{ + value_at_pixel_pos_.clear(); + current_pixel_pos_ = -1; + prev_value_at_pixel_ = std::numeric_limits::quiet_NaN(); +} + +void AnalogSignal::process_next_sample_value(float x, float value) +{ + // Note: NAN is used to indicate the non-existance of a value at this pixel + + if (std::isnan(prev_value_at_pixel_)) { + if (x < 0) { + min_value_at_pixel_ = value; + max_value_at_pixel_ = value; + prev_value_at_pixel_ = value; + current_pixel_pos_ = x; + } else + prev_value_at_pixel_ = std::numeric_limits::quiet_NaN(); + } + + const int pixel_pos = (int)(x + 0.5); + + if (pixel_pos > current_pixel_pos_) { + if (pixel_pos - current_pixel_pos_ == 1) { + if (std::isnan(prev_value_at_pixel_)) { + value_at_pixel_pos_.push_back(prev_value_at_pixel_); + } else { + // Average the min/max range to create one value for the previous pixel + const float avg = (min_value_at_pixel_ + max_value_at_pixel_) / 2; + value_at_pixel_pos_.push_back(avg); + } + } else { + // Interpolate values to create values for the intermediate pixels + const float start_value = prev_value_at_pixel_; + const float end_value = value; + const int steps = fabs(pixel_pos - current_pixel_pos_); + const double gradient = (end_value - start_value) / steps; + for (int i = 0; i < steps; i++) { + if (current_pixel_pos_ + i < 0) + continue; + value_at_pixel_pos_.push_back(start_value + i * gradient); + } + } + + min_value_at_pixel_ = value; + max_value_at_pixel_ = value; + prev_value_at_pixel_ = value; + current_pixel_pos_ = pixel_pos; + } else { + // Another sample for the same pixel + if (value < min_value_at_pixel_) + min_value_at_pixel_ = value; + if (value > max_value_at_pixel_) + max_value_at_pixel_ = value; + } +} + void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form) { // Add the standard options @@ -833,7 +978,7 @@ void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form) connect(conv_threshold_cb_, SIGNAL(currentIndexChanged(int)), this, SLOT(on_conv_threshold_changed(int))); - connect(conv_threshold_cb_, SIGNAL(editTextChanged(const QString)), + connect(conv_threshold_cb_, SIGNAL(editTextChanged(const QString&)), this, SLOT(on_conv_threshold_changed())); // index will be -1 // Add the display type dropdown @@ -857,6 +1002,24 @@ void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form) form->addRow(layout); } +void AnalogSignal::hover_point_changed(const QPoint &hp) +{ + Signal::hover_point_changed(hp); + + // Note: Even though the view area begins at 0, we exclude 0 because + // that's also the value given when the cursor is over the header to the + // left of the trace paint area + if (hp.x() <= 0) { + value_at_hover_pos_ = std::numeric_limits::quiet_NaN(); + } else { + try { + value_at_hover_pos_ = value_at_pixel_pos_.at(hp.x()); + } catch (out_of_range&) { + value_at_hover_pos_ = std::numeric_limits::quiet_NaN(); + } + } +} + void AnalogSignal::on_min_max_changed(float min, float max) { (void)min; @@ -1070,6 +1233,14 @@ void AnalogSignal::on_display_type_changed(int index) owner_->row_item_appearance_changed(false, true); } +void AnalogSignal::on_settingViewConversionThresholdDispMode_changed(const QVariant new_value) +{ + conversion_threshold_disp_mode_ = new_value.toInt(); + + if (owner_) + owner_->row_item_appearance_changed(false, true); +} + } // namespace trace } // namespace views } // namespace pv