+void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp)
+{
+ QLineF *line;
+
+ vector< pair<int64_t, bool> > edges;
+
+ assert(base_);
+
+ const int y = get_visual_y();
+
+ if (!base_->enabled() || !base_->logic_data())
+ return;
+
+ const int signal_margin =
+ QFontMetrics(QApplication::font()).height() / 2;
+
+ const int ph = min(pos_vdivs_, 1) * div_height_;
+ const int nh = min(neg_vdivs_, 1) * div_height_;
+ const float high_offset = y - ph + signal_margin + 0.5f;
+ const float low_offset = y + nh - signal_margin - 0.5f;
+
+ const deque< shared_ptr<pv::data::LogicSegment> > &segments =
+ base_->logic_data()->logic_segments();
+
+ if (segments.empty())
+ return;
+
+ const shared_ptr<pv::data::LogicSegment> &segment =
+ segments.front();
+
+ double samplerate = segment->samplerate();
+
+ // Show sample rate as 1Hz when it is unknown
+ if (samplerate == 0.0)
+ samplerate = 1.0;
+
+ 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 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();
+
+ const int64_t start_sample = min(max(floor(start).convert_to<int64_t>(),
+ (int64_t)0), last_sample);
+ const uint64_t end_sample = min(max(ceil(end).convert_to<int64_t>(),
+ (int64_t)0), last_sample);
+
+ segment->get_subsampled_edges(edges, start_sample, end_sample,
+ samples_per_pixel / LogicSignal::Oversampling, 0);
+ assert(edges.size() >= 2);
+
+ // Paint the edges
+ const unsigned int edge_count = edges.size() - 2;
+ QLineF *const edge_lines = new QLineF[edge_count];
+ line = edge_lines;
+
+ for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) {
+ const float x = ((*i).first / samples_per_pixel -
+ pixels_offset) + pp.left();
+ *line++ = QLineF(x, high_offset, x, low_offset);
+ }
+
+ p.setPen(LogicSignal::EdgeColour);
+ p.drawLines(edge_lines, edge_count);
+ delete[] edge_lines;
+
+ // Paint the caps
+ const unsigned int max_cap_line_count = edges.size();
+ QLineF *const cap_lines = new QLineF[max_cap_line_count];
+
+ p.setPen(LogicSignal::HighColour);
+ paint_logic_caps(p, cap_lines, edges, true, samples_per_pixel,
+ pixels_offset, pp.left(), high_offset);
+ p.setPen(LogicSignal::LowColour);
+ paint_logic_caps(p, cap_lines, edges, false, samples_per_pixel,
+ pixels_offset, pp.left(), low_offset);
+
+ delete[] cap_lines;
+
+ // Return if we don't need to paint the sampling points
+ GlobalSettings settings;
+ const bool show_sampling_points =
+ settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool();
+
+ if (!show_sampling_points || (samples_per_pixel >= 0.25))
+ return;
+
+ // Paint the sampling points
+ const uint64_t sampling_points_count = end_sample - start_sample + 1;
+ QRectF *const sampling_points = new QRectF[sampling_points_count];
+ QRectF *sampling_point = sampling_points;
+
+ const int w = 1;
+ const float y_middle = high_offset - ((high_offset - low_offset) / 2);
+ for (uint64_t i = start_sample; i < end_sample + 1; ++i) {
+ const float x = (i / samples_per_pixel - pixels_offset) + pp.left();
+ *sampling_point++ = QRectF(x - (w / 2), y_middle - (w / 2), w, w);
+ }
+
+ p.setPen(SamplingPointColour);
+ p.drawRects(sampling_points, sampling_points_count);
+ delete[] sampling_points;
+}
+
+void AnalogSignal::paint_logic_caps(QPainter &p, QLineF *const lines,
+ vector< pair<int64_t, bool> > &edges, bool level,
+ double samples_per_pixel, double pixels_offset, float x_offset,
+ float y_offset)
+{
+ QLineF *line = lines;
+
+ for (auto i = edges.begin(); i != (edges.end() - 1); i++)
+ if ((*i).second == level) {
+ *line++ = QLineF(
+ ((*i).first / samples_per_pixel -
+ pixels_offset) + x_offset, y_offset,
+ ((*(i+1)).first / samples_per_pixel -
+ pixels_offset) + x_offset, y_offset);
+ }
+
+ p.drawLines(lines, line - lines);
+}
+
+float AnalogSignal::get_resolution(int scale_index)
+{
+ const float seq[] = {1.0f, 2.0f, 5.0f};
+
+ const int offset = numeric_limits<int>::max() / (2 * countof(seq));
+ const div_t d = div((int)(scale_index + countof(seq) * offset),
+ countof(seq));
+
+ return powf(10.0f, d.quot - offset) * seq[d.rem];
+}
+
+void AnalogSignal::update_scale()
+{
+ resolution_ = get_resolution(scale_index_);
+ scale_ = div_height_ / resolution_;
+}
+
+void AnalogSignal::update_conversion_type()
+{
+ base_->set_conversion_type(conversion_type_);
+
+ if (owner_)
+ owner_->row_item_appearance_changed(false, true);
+}
+
+void AnalogSignal::perform_autoranging(bool force_update)
+{
+ const deque< shared_ptr<pv::data::AnalogSegment> > &segments =
+ base_->analog_data()->analog_segments();
+
+ if (segments.empty())
+ return;
+
+ static double prev_min = 0, prev_max = 0;
+ double min = 0, max = 0;
+
+ for (shared_ptr<pv::data::AnalogSegment> segment : segments) {
+ pair<double, double> mm = segment->get_min_max();
+ min = std::min(min, mm.first);
+ max = std::max(max, mm.second);
+ }
+
+ if ((min == prev_min) && (max == prev_max) && !force_update)
+ return;
+
+ prev_min = min;
+ prev_max = max;
+
+ // Use all divs for the positive range if there are no negative values
+ if ((min == 0) && (neg_vdivs_ > 0)) {
+ pos_vdivs_ += neg_vdivs_;
+ neg_vdivs_ = 0;
+ }
+
+ // Split up the divs if there are negative values but no negative divs
+ if ((min < 0) && (neg_vdivs_ == 0)) {
+ neg_vdivs_ = pos_vdivs_ / 2;
+ pos_vdivs_ -= neg_vdivs_;
+ }
+
+ double min_value_per_div;
+ if ((pos_vdivs_ > 0) && (neg_vdivs_ > 0))
+ min_value_per_div = std::max(max / pos_vdivs_, -min / neg_vdivs_);
+ else if (pos_vdivs_ > 0)
+ min_value_per_div = max / pos_vdivs_;
+ else
+ min_value_per_div = -min / neg_vdivs_;
+
+ // Find first scale value that is bigger than the value we need
+ for (int i = MinScaleIndex; i < MaxScaleIndex; i++)
+ if (get_resolution(i) > min_value_per_div) {
+ scale_index_ = i;
+ break;
+ }
+
+ update_scale();
+}
+
+void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
+{
+ // Add the standard options
+ Signal::populate_popup_form(parent, form);
+
+ QFormLayout *const layout = new QFormLayout;
+
+ // Add the number of vdivs
+ QSpinBox *pvdiv_sb = new QSpinBox(parent);
+ pvdiv_sb->setRange(0, MaximumVDivs);
+ pvdiv_sb->setValue(pos_vdivs_);
+ connect(pvdiv_sb, SIGNAL(valueChanged(int)),
+ this, SLOT(on_pos_vdivs_changed(int)));
+ layout->addRow(tr("Number of pos vertical divs"), pvdiv_sb);
+
+ QSpinBox *nvdiv_sb = new QSpinBox(parent);
+ nvdiv_sb->setRange(0, MaximumVDivs);
+ nvdiv_sb->setValue(neg_vdivs_);
+ connect(nvdiv_sb, SIGNAL(valueChanged(int)),
+ this, SLOT(on_neg_vdivs_changed(int)));
+ layout->addRow(tr("Number of neg vertical divs"), nvdiv_sb);
+
+ // Add the vertical resolution
+ resolution_cb_ = new QComboBox(parent);
+
+ for (int i = MinScaleIndex; i < MaxScaleIndex; i++) {
+ const QString label = QString("%1").arg(get_resolution(i));
+ resolution_cb_->insertItem(0, label, QVariant(i));
+ }
+
+ int cur_idx = resolution_cb_->findData(QVariant(scale_index_));
+ resolution_cb_->setCurrentIndex(cur_idx);
+
+ connect(resolution_cb_, SIGNAL(currentIndexChanged(int)),
+ this, SLOT(on_resolution_changed(int)));
+
+ QGridLayout *const vdiv_layout = new QGridLayout;
+ QLabel *const vdiv_unit = new QLabel(tr("V/div"));
+ vdiv_layout->addWidget(resolution_cb_, 0, 0);
+ vdiv_layout->addWidget(vdiv_unit, 0, 1);
+
+ layout->addRow(tr("Vertical resolution"), vdiv_layout);
+
+ // Add the autoranging checkbox
+ QCheckBox* autoranging_cb = new QCheckBox();
+ autoranging_cb->setCheckState(autoranging_ ? Qt::Checked : Qt::Unchecked);
+
+ connect(autoranging_cb, SIGNAL(stateChanged(int)),
+ this, SLOT(on_autoranging_changed(int)));
+
+ layout->addRow(tr("Autoranging"), autoranging_cb);
+
+ // Add the conversion type dropdown
+ conversion_cb_ = new QComboBox();
+
+ conversion_cb_->addItem("none", data::SignalBase::NoConversion);
+ conversion_cb_->addItem("to logic via threshold", data::SignalBase::A2LConversionByTreshold);
+ conversion_cb_->addItem("to logic via schmitt-trigger", data::SignalBase::A2LConversionBySchmittTrigger);
+
+ cur_idx = conversion_cb_->findData(QVariant(conversion_type_));
+ conversion_cb_->setCurrentIndex(cur_idx);
+
+ layout->addRow(tr("Conversion"), conversion_cb_);
+
+ connect(conversion_cb_, SIGNAL(currentIndexChanged(int)),
+ this, SLOT(on_conversion_changed(int)));
+
+ // Add the display type dropdown
+ display_type_cb_ = new QComboBox();
+
+ display_type_cb_->addItem(tr("Analog"), DisplayAnalog);
+ display_type_cb_->addItem(tr("Converted"), DisplayConverted);
+ display_type_cb_->addItem(tr("Both"), DisplayBoth);
+
+ cur_idx = display_type_cb_->findData(QVariant(display_type_));
+ display_type_cb_->setCurrentIndex(cur_idx);
+
+ layout->addRow(tr("Traces to show:"), display_type_cb_);
+
+ connect(display_type_cb_, SIGNAL(currentIndexChanged(int)),
+ this, SLOT(on_display_type_changed(int)));
+
+ form->addRow(layout);
+}
+
+void AnalogSignal::on_samples_added()
+{
+ perform_autoranging();
+
+ if (owner_) {
+ // Call order is important, otherwise the lazy event handler won't work
+ owner_->extents_changed(false, true);
+ owner_->row_item_appearance_changed(false, true);
+ }
+}
+
+void AnalogSignal::on_pos_vdivs_changed(int vdivs)
+{
+ pos_vdivs_ = vdivs;
+
+ if (autoranging_)
+ perform_autoranging(true);
+
+ if (owner_) {
+ // Call order is important, otherwise the lazy event handler won't work
+ owner_->extents_changed(false, true);
+ owner_->row_item_appearance_changed(false, true);
+ }
+}
+
+void AnalogSignal::on_neg_vdivs_changed(int vdivs)
+{
+ neg_vdivs_ = vdivs;
+
+ if (autoranging_)
+ perform_autoranging(true);
+
+ if (owner_) {
+ // Call order is important, otherwise the lazy event handler won't work
+ owner_->extents_changed(false, true);
+ owner_->row_item_appearance_changed(false, true);
+ }
+}
+
+void AnalogSignal::on_resolution_changed(int index)
+{
+ scale_index_ = resolution_cb_->itemData(index).toInt();
+ update_scale();
+
+ if (owner_)
+ owner_->row_item_appearance_changed(false, true);
+}
+
+void AnalogSignal::on_autoranging_changed(int state)
+{
+ autoranging_ = (state == Qt::Checked);
+
+ if (autoranging_)
+ perform_autoranging(true);
+
+ if (owner_) {
+ // Call order is important, otherwise the lazy event handler won't work
+ owner_->extents_changed(false, true);
+ owner_->row_item_appearance_changed(false, true);
+ }
+}
+
+void AnalogSignal::on_conversion_changed(int index)
+{
+ data::SignalBase::ConversionType old_conv_type = conversion_type_;
+
+ conversion_type_ = (data::SignalBase::ConversionType)(conversion_cb_->itemData(index).toInt());
+
+ if (conversion_type_ != old_conv_type) {
+ base_->set_conversion_type(conversion_type_);
+ update_conversion_type();
+ }
+}
+
+void AnalogSignal::on_display_type_changed(int index)
+{
+ display_type_ = (DisplayType)(display_type_cb_->itemData(index).toInt());
+
+ if (owner_)
+ owner_->row_item_appearance_changed(false, true);
+}
+
+} // namespace TraceView
+} // namespace views