X-Git-Url: https://sigrok.org/gitweb/?p=pulseview.git;a=blobdiff_plain;f=pv%2Fview%2Fdecodetrace.cpp;h=22d8f2dce06ceff5b0c46e8a185550ce52fc81ba;hp=b67c0f7abeb256d0ae472f456d878b9cca5c468c;hb=dd048a7ec035447c051c7937ce04aca5be065b4e;hpb=5069084a51b248fceaa123f451bad3d8fb01d2fb diff --git a/pv/view/decodetrace.cpp b/pv/view/decodetrace.cpp index b67c0f7a..22d8f2dc 100644 --- a/pv/view/decodetrace.cpp +++ b/pv/view/decodetrace.cpp @@ -27,6 +27,7 @@ extern "C" { #include #include +#include #include #include #include @@ -38,14 +39,21 @@ extern "C" { #include #include #include +#include +#include +#include #include #include -#include #include #include -using namespace boost; -using namespace std; +using boost::dynamic_pointer_cast; +using boost::shared_ptr; +using std::list; +using std::max; +using std::map; +using std::min; +using std::vector; namespace pv { namespace view { @@ -58,12 +66,28 @@ const QColor DecodeTrace::DecodeColours[4] = { }; const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29); +const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85); + +const double DecodeTrace::EndCapWidth = 5; +const int DecodeTrace::DrawPadding = 100; + +const QColor DecodeTrace::Colours[7] = { + QColor(0xFC, 0xE9, 0x4F), // Light Butter + QColor(0xFC, 0xAF, 0x3E), // Light Orange + QColor(0xE9, 0xB9, 0x6E), // Light Chocolate + QColor(0x8A, 0xE2, 0x34), // Light Green + QColor(0x72, 0x9F, 0xCF), // Light Blue + QColor(0xAD, 0x7F, 0xA8), // Light Plum + QColor(0xEF, 0x29, 0x29) // Light Red +}; DecodeTrace::DecodeTrace(pv::SigSession &session, boost::shared_ptr decoder_stack, int index) : - Trace(session, QString(decoder_stack->stack().front()->decoder()->name)), + Trace(session, QString::fromUtf8( + decoder_stack->stack().front()->decoder()->name)), _decoder_stack(decoder_stack), - _delete_mapper(this) + _delete_mapper(this), + _show_hide_mapper(this) { assert(_decoder_stack); @@ -73,6 +97,8 @@ DecodeTrace::DecodeTrace(pv::SigSession &session, this, SLOT(on_new_decode_data())); connect(&_delete_mapper, SIGNAL(mapped(int)), this, SLOT(on_delete_decoder(int))); + connect(&_show_hide_mapper, SIGNAL(mapped(int)), + this, SLOT(on_show_hide_decoder(int))); } bool DecodeTrace::enabled() const @@ -93,27 +119,18 @@ void DecodeTrace::set_view(pv::view::View *view) void DecodeTrace::paint_back(QPainter &p, int left, int right) { + Trace::paint_back(p, left, right); paint_axis(p, get_y(), left, right); } void DecodeTrace::paint_mid(QPainter &p, int left, int right) { - using namespace pv::view::decode; - - assert(_decoder_stack); - const QString err = _decoder_stack->error_message(); - if (!err.isEmpty()) { - draw_error(p, err, left, right); - return; - } - - assert(_view); - const int y = get_y(); + using namespace pv::data::decode; const double scale = _view->scale(); assert(scale > 0); - double samplerate = _decoder_stack->get_samplerate(); + double samplerate = _decoder_stack->samplerate(); // Show sample rate as 1Hz when it is unknown if (samplerate == 0.0) @@ -123,13 +140,51 @@ void DecodeTrace::paint_mid(QPainter &p, int left, int right) _decoder_stack->get_start_time()) / scale; const double samples_per_pixel = samplerate * scale; + const uint64_t start_sample = (uint64_t)max((left + pixels_offset) * + samples_per_pixel, 0.0); + const uint64_t end_sample = (uint64_t)max((right + pixels_offset) * + samples_per_pixel, 0.0); + + QFontMetrics m(QApplication::font()); + const int text_height = m.boundingRect(QRect(), 0, "Tg").height(); + const int annotation_height = (text_height * 5) / 4; + const int row_height = (text_height * 6) / 4; + assert(_decoder_stack); - vector< shared_ptr > annotations(_decoder_stack->annotations()); - BOOST_FOREACH(shared_ptr a, annotations) { - assert(a); - a->paint(p, get_text_colour(), _text_size.height(), - left, right, samples_per_pixel, pixels_offset, y); + const QString err = _decoder_stack->error_message(); + if (!err.isEmpty()) + { + draw_error(p, err, left, right); + draw_unresolved_period(p, annotation_height, left, right, + samples_per_pixel, pixels_offset); + return; } + + // Iterate through the rows + assert(_view); + int y = get_y(); + + assert(_decoder_stack); + + const vector rows(_decoder_stack->get_visible_rows()); + for (size_t i = 0; i < rows.size(); i++) + { + const Row &row = rows[i]; + vector annotations; + _decoder_stack->get_annotation_subset(annotations, row, + start_sample, end_sample); + if (!annotations.empty()) { + BOOST_FOREACH(const Annotation &a, annotations) + draw_annotation(a, p, get_text_colour(), + annotation_height, left, right, + samples_per_pixel, pixels_offset, y, i); + y += row_height; + } + } + + // Draw the hatching + draw_unresolved_period(p, annotation_height, left, right, + samples_per_pixel, pixels_offset); } void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form) @@ -146,6 +201,7 @@ void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form) // Add the decoder options _bindings.clear(); _probe_selectors.clear(); + _decoder_forms.clear(); const list< shared_ptr >& stack = _decoder_stack->stack(); @@ -198,6 +254,102 @@ QMenu* DecodeTrace::create_context_menu(QWidget *parent) return menu; } +void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a, QPainter &p, + QColor text_color, int h, int left, int right, double samples_per_pixel, + double pixels_offset, int y, unsigned int row_index) const +{ + const double start = a.start_sample() / samples_per_pixel - + pixels_offset; + const double end = a.end_sample() / samples_per_pixel - + pixels_offset; + const QColor fill = Colours[((row_index * 2 + a.format()) * + (countof(Colours) / 4 + 1)) % countof(Colours)]; + const QColor outline(fill.darker()); + + if (start > right + DrawPadding || end < left - DrawPadding) + return; + + if (a.start_sample() == a.end_sample()) + draw_instant(a, p, fill, outline, text_color, h, + start, y); + else + draw_range(a, p, fill, outline, text_color, h, + start, end, y); +} + +void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p, + QColor fill, QColor outline, QColor text_color, int h, double x, int y) const +{ + const QString text = a.annotations().empty() ? + QString() : a.annotations().back(); + const double w = min(p.boundingRect(QRectF(), 0, text).width(), + 0.0) + h; + const QRectF rect(x - w / 2, y - h / 2, w, h); + + p.setPen(outline); + p.setBrush(fill); + p.drawRoundedRect(rect, h / 2, h / 2); + + p.setPen(text_color); + p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text); +} + +void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p, + QColor fill, QColor outline, QColor text_color, int h, double start, + double end, int y) const +{ + const double top = y + .5 - h / 2; + const double bottom = y + .5 + h / 2; + const vector annotations = a.annotations(); + + p.setPen(outline); + p.setBrush(fill); + + // If the two ends are within 1 pixel, draw a vertical line + if (start + 1.0 > end) + { + p.drawLine(QPointF(start, top), QPointF(start, bottom)); + return; + } + + const double cap_width = min((end - start) / 4, EndCapWidth); + + QPointF pts[] = { + QPointF(start, y + .5f), + QPointF(start + cap_width, top), + QPointF(end - cap_width, top), + QPointF(end, y + .5f), + QPointF(end - cap_width, bottom), + QPointF(start + cap_width, bottom) + }; + + p.drawConvexPolygon(pts, countof(pts)); + + if (annotations.empty()) + return; + + QRectF rect(start + cap_width, y - h / 2, + end - start - cap_width * 2, h); + p.setPen(text_color); + + // Try to find an annotation that will fit + QString best_annotation; + int best_width = 0; + + BOOST_FOREACH(const QString &a, annotations) { + const int w = p.boundingRect(QRectF(), 0, a).width(); + if (w <= rect.width() && w > best_width) + best_annotation = a, best_width = w; + } + + if (best_annotation.isEmpty()) + best_annotation = annotations.back(); + + // If not ellide the last in the list + p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText( + best_annotation, Qt::ElideRight, rect.width())); +} + void DecodeTrace::draw_error(QPainter &p, const QString &message, int left, int right) { @@ -219,6 +371,58 @@ void DecodeTrace::draw_error(QPainter &p, const QString &message, p.drawText(text_rect, message); } +void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, + int right, double samples_per_pixel, double pixels_offset) +{ + using namespace pv::data; + using pv::data::decode::Decoder; + + assert(_decoder_stack); + + shared_ptr data; + shared_ptr logic_signal; + + const list< shared_ptr > &stack = _decoder_stack->stack(); + + // We get the logic data of the first probe in the list. + // This works because we are currently assuming all + // LogicSignals have the same data/snapshot + BOOST_FOREACH (const shared_ptr &dec, stack) + if (dec && !dec->probes().empty() && + ((logic_signal = (*dec->probes().begin()).second)) && + ((data = logic_signal->logic_data()))) + break; + + if (!data || data->get_snapshots().empty()) + return; + + const shared_ptr snapshot = + data->get_snapshots().front(); + assert(snapshot); + const int64_t sample_count = (int64_t)snapshot->get_sample_count(); + if (sample_count == 0) + return; + + const int64_t samples_decoded = _decoder_stack->samples_decoded(); + if (sample_count == samples_decoded) + return; + + const int y = get_y(); + const double start = max(samples_decoded / + samples_per_pixel - pixels_offset, left - 1.0); + const double end = min(sample_count / samples_per_pixel - + pixels_offset, right + 1.0); + const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h); + + p.setPen(QPen(Qt::NoPen)); + p.setBrush(Qt::white); + p.drawRect(no_decode_rect); + + p.setPen(NoDecodeColour); + p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern)); + p.drawRect(no_decode_rect); +} + void DecodeTrace::create_decoder_form(int index, shared_ptr &dec, QWidget *parent, QFormLayout *form) @@ -230,11 +434,17 @@ void DecodeTrace::create_decoder_form(int index, assert(decoder); pv::widgets::DecoderGroupBox *const group = - new pv::widgets::DecoderGroupBox(decoder->name); + new pv::widgets::DecoderGroupBox( + QString::fromUtf8(decoder->name)); + group->set_decoder_visible(dec->shown()); _delete_mapper.setMapping(group, index); connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map())); + _show_hide_mapper.setMapping(group, index); + connect(group, SIGNAL(show_hide_decoder()), + &_show_hide_mapper, SLOT(map())); + QFormLayout *const decoder_form = new QFormLayout; group->add_layout(decoder_form); @@ -274,6 +484,7 @@ void DecodeTrace::create_decoder_form(int index, _bindings.push_back(binding); form->addRow(group); + _decoder_forms.push_back(group); } QComboBox* DecodeTrace::create_probe_selector( @@ -391,5 +602,28 @@ void DecodeTrace::on_delete_decoder(int index) _decoder_stack->begin_decode(); } +void DecodeTrace::on_show_hide_decoder(int index) +{ + using pv::data::decode::Decoder; + + const list< shared_ptr > stack(_decoder_stack->stack()); + + // Find the decoder in the stack + list< shared_ptr >::const_iterator iter = stack.begin(); + for(int i = 0; i < index; i++, iter++) + assert(iter != stack.end()); + + shared_ptr dec = *iter; + assert(dec); + + const bool show = !dec->shown(); + dec->show(show); + + assert(index < (int)_decoder_forms.size()); + _decoder_forms[index]->set_decoder_visible(show); + + _view->update_viewport(); +} + } // namespace view } // namespace pv