]> sigrok.org Git - pulseview.git/blame_incremental - pv/view/decodetrace.cpp
Added DevInst pointer to Signal
[pulseview.git] / pv / view / decodetrace.cpp
... / ...
CommitLineData
1/*
2 * This file is part of the PulseView project.
3 *
4 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
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
21extern "C" {
22#include <libsigrokdecode/libsigrokdecode.h>
23}
24
25#include <extdef.h>
26
27#include <boost/foreach.hpp>
28
29#include <QAction>
30#include <QApplication>
31#include <QComboBox>
32#include <QFormLayout>
33#include <QLabel>
34#include <QMenu>
35#include <QPushButton>
36
37#include "decodetrace.h"
38
39#include <pv/sigsession.h>
40#include <pv/data/decoderstack.h>
41#include <pv/data/decode/decoder.h>
42#include <pv/data/logic.h>
43#include <pv/data/logicsnapshot.h>
44#include <pv/data/decode/annotation.h>
45#include <pv/view/logicsignal.h>
46#include <pv/view/view.h>
47#include <pv/widgets/decodergroupbox.h>
48#include <pv/widgets/decodermenu.h>
49
50using boost::dynamic_pointer_cast;
51using boost::shared_ptr;
52using std::list;
53using std::max;
54using std::map;
55using std::min;
56using std::vector;
57
58namespace pv {
59namespace view {
60
61const QColor DecodeTrace::DecodeColours[4] = {
62 QColor(0xEF, 0x29, 0x29), // Red
63 QColor(0xFC, 0xE9, 0x4F), // Yellow
64 QColor(0x8A, 0xE2, 0x34), // Green
65 QColor(0x72, 0x9F, 0xCF) // Blue
66};
67
68const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
69const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
70
71const double DecodeTrace::EndCapWidth = 5;
72const int DecodeTrace::DrawPadding = 100;
73
74const QColor DecodeTrace::Colours[16] = {
75 QColor(0xEF, 0x29, 0x29),
76 QColor(0xF6, 0x6A, 0x32),
77 QColor(0xFC, 0xAE, 0x3E),
78 QColor(0xFB, 0xCA, 0x47),
79 QColor(0xFC, 0xE9, 0x4F),
80 QColor(0xCD, 0xF0, 0x40),
81 QColor(0x8A, 0xE2, 0x34),
82 QColor(0x4E, 0xDC, 0x44),
83 QColor(0x55, 0xD7, 0x95),
84 QColor(0x64, 0xD1, 0xD2),
85 QColor(0x72, 0x9F, 0xCF),
86 QColor(0xD4, 0x76, 0xC4),
87 QColor(0x9D, 0x79, 0xB9),
88 QColor(0xAD, 0x7F, 0xA8),
89 QColor(0xC2, 0x62, 0x9B),
90 QColor(0xD7, 0x47, 0x6F)
91};
92
93const QColor DecodeTrace::OutlineColours[16] = {
94 QColor(0x77, 0x14, 0x14),
95 QColor(0x7B, 0x35, 0x19),
96 QColor(0x7E, 0x57, 0x1F),
97 QColor(0x7D, 0x65, 0x23),
98 QColor(0x7E, 0x74, 0x27),
99 QColor(0x66, 0x78, 0x20),
100 QColor(0x45, 0x71, 0x1A),
101 QColor(0x27, 0x6E, 0x22),
102 QColor(0x2A, 0x6B, 0x4A),
103 QColor(0x32, 0x68, 0x69),
104 QColor(0x39, 0x4F, 0x67),
105 QColor(0x6A, 0x3B, 0x62),
106 QColor(0x4E, 0x3C, 0x5C),
107 QColor(0x56, 0x3F, 0x54),
108 QColor(0x61, 0x31, 0x4D),
109 QColor(0x6B, 0x23, 0x37)
110};
111
112DecodeTrace::DecodeTrace(pv::SigSession &session,
113 boost::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
114 Trace(session, QString::fromUtf8(
115 decoder_stack->stack().front()->decoder()->name)),
116 _decoder_stack(decoder_stack),
117 _delete_mapper(this),
118 _show_hide_mapper(this)
119{
120 assert(_decoder_stack);
121
122 _colour = DecodeColours[index % countof(DecodeColours)];
123
124 connect(_decoder_stack.get(), SIGNAL(new_decode_data()),
125 this, SLOT(on_new_decode_data()));
126 connect(&_delete_mapper, SIGNAL(mapped(int)),
127 this, SLOT(on_delete_decoder(int)));
128 connect(&_show_hide_mapper, SIGNAL(mapped(int)),
129 this, SLOT(on_show_hide_decoder(int)));
130}
131
132bool DecodeTrace::enabled() const
133{
134 return true;
135}
136
137const boost::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
138{
139 return _decoder_stack;
140}
141
142void DecodeTrace::set_view(pv::view::View *view)
143{
144 assert(view);
145 Trace::set_view(view);
146}
147
148void DecodeTrace::paint_back(QPainter &p, int left, int right)
149{
150 Trace::paint_back(p, left, right);
151 paint_axis(p, get_y(), left, right);
152}
153
154void DecodeTrace::paint_mid(QPainter &p, int left, int right)
155{
156 using namespace pv::data::decode;
157
158 const double scale = _view->scale();
159 assert(scale > 0);
160
161 double samplerate = _decoder_stack->samplerate();
162
163 // Show sample rate as 1Hz when it is unknown
164 if (samplerate == 0.0)
165 samplerate = 1.0;
166
167 const double pixels_offset = (_view->offset() -
168 _decoder_stack->get_start_time()) / scale;
169 const double samples_per_pixel = samplerate * scale;
170
171 const uint64_t start_sample = (uint64_t)max((left + pixels_offset) *
172 samples_per_pixel, 0.0);
173 const uint64_t end_sample = (uint64_t)max((right + pixels_offset) *
174 samples_per_pixel, 0.0);
175
176 QFontMetrics m(QApplication::font());
177 const int text_height = m.boundingRect(QRect(), 0, "Tg").height();
178 const int annotation_height = (text_height * 5) / 4;
179 const int row_height = (text_height * 6) / 4;
180
181 assert(_decoder_stack);
182 const QString err = _decoder_stack->error_message();
183 if (!err.isEmpty())
184 {
185 draw_unresolved_period(p, annotation_height, left, right,
186 samples_per_pixel, pixels_offset);
187 draw_error(p, err, left, right);
188 return;
189 }
190
191 // Iterate through the rows
192 assert(_view);
193 int y = get_y();
194
195 assert(_decoder_stack);
196
197 const vector<Row> rows(_decoder_stack->get_visible_rows());
198 for (size_t i = 0; i < rows.size(); i++)
199 {
200 const Row &row = rows[i];
201
202 size_t base_colour = 0x13579BDF;
203 boost::hash_combine(base_colour, this);
204 boost::hash_combine(base_colour, row.decoder());
205 boost::hash_combine(base_colour, row.row());
206 base_colour >>= 16;
207
208 vector<Annotation> annotations;
209 _decoder_stack->get_annotation_subset(annotations, row,
210 start_sample, end_sample);
211 if (!annotations.empty()) {
212 BOOST_FOREACH(const Annotation &a, annotations)
213 draw_annotation(a, p, get_text_colour(),
214 annotation_height, left, right,
215 samples_per_pixel, pixels_offset, y,
216 base_colour);
217 y += row_height;
218 }
219 }
220
221 // Draw the hatching
222 draw_unresolved_period(p, annotation_height, left, right,
223 samples_per_pixel, pixels_offset);
224}
225
226void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
227{
228 using pv::data::decode::Decoder;
229
230 assert(form);
231 assert(parent);
232 assert(_decoder_stack);
233
234 // Add the standard options
235 Trace::populate_popup_form(parent, form);
236
237 // Add the decoder options
238 _bindings.clear();
239 _probe_selectors.clear();
240 _decoder_forms.clear();
241
242 const list< shared_ptr<Decoder> >& stack = _decoder_stack->stack();
243
244 if (stack.empty())
245 {
246 QLabel *const l = new QLabel(
247 tr("<p><i>No decoders in the stack</i></p>"));
248 l->setAlignment(Qt::AlignCenter);
249 form->addRow(l);
250 }
251 else
252 {
253 list< shared_ptr<Decoder> >::const_iterator iter =
254 stack.begin();
255 for (int i = 0; i < (int)stack.size(); i++, iter++) {
256 shared_ptr<Decoder> dec(*iter);
257 create_decoder_form(i, dec, parent, form);
258 }
259
260 form->addRow(new QLabel(
261 tr("<i>* Required Probes</i>"), parent));
262 }
263
264 // Add stacking button
265 pv::widgets::DecoderMenu *const decoder_menu =
266 new pv::widgets::DecoderMenu(parent);
267 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
268 this, SLOT(on_stack_decoder(srd_decoder*)));
269
270 QPushButton *const stack_button =
271 new QPushButton(tr("Stack Decoder"), parent);
272 stack_button->setMenu(decoder_menu);
273
274 QHBoxLayout *stack_button_box = new QHBoxLayout;
275 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
276 form->addRow(stack_button_box);
277}
278
279QMenu* DecodeTrace::create_context_menu(QWidget *parent)
280{
281 QMenu *const menu = Trace::create_context_menu(parent);
282
283 menu->addSeparator();
284
285 QAction *const del = new QAction(tr("Delete"), this);
286 del->setShortcuts(QKeySequence::Delete);
287 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
288 menu->addAction(del);
289
290 return menu;
291}
292
293void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
294 QPainter &p, QColor text_color, int h, int left, int right,
295 double samples_per_pixel, double pixels_offset, int y,
296 size_t base_colour) const
297{
298 const double start = a.start_sample() / samples_per_pixel -
299 pixels_offset;
300 const double end = a.end_sample() / samples_per_pixel -
301 pixels_offset;
302
303 const size_t colour = (base_colour + a.format()) % countof(Colours);
304 const QColor &fill = Colours[colour];
305 const QColor &outline = OutlineColours[colour];
306
307 if (start > right + DrawPadding || end < left - DrawPadding)
308 return;
309
310 if (a.start_sample() == a.end_sample())
311 draw_instant(a, p, fill, outline, text_color, h,
312 start, y);
313 else
314 draw_range(a, p, fill, outline, text_color, h,
315 start, end, y);
316}
317
318void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
319 QColor fill, QColor outline, QColor text_color, int h, double x, int y) const
320{
321 const QString text = a.annotations().empty() ?
322 QString() : a.annotations().back();
323 const double w = min(p.boundingRect(QRectF(), 0, text).width(),
324 0.0) + h;
325 const QRectF rect(x - w / 2, y - h / 2, w, h);
326
327 p.setPen(outline);
328 p.setBrush(fill);
329 p.drawRoundedRect(rect, h / 2, h / 2);
330
331 p.setPen(text_color);
332 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
333}
334
335void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
336 QColor fill, QColor outline, QColor text_color, int h, double start,
337 double end, int y) const
338{
339 const double top = y + .5 - h / 2;
340 const double bottom = y + .5 + h / 2;
341 const vector<QString> annotations = a.annotations();
342
343 p.setPen(outline);
344 p.setBrush(fill);
345
346 // If the two ends are within 1 pixel, draw a vertical line
347 if (start + 1.0 > end)
348 {
349 p.drawLine(QPointF(start, top), QPointF(start, bottom));
350 return;
351 }
352
353 const double cap_width = min((end - start) / 4, EndCapWidth);
354
355 QPointF pts[] = {
356 QPointF(start, y + .5f),
357 QPointF(start + cap_width, top),
358 QPointF(end - cap_width, top),
359 QPointF(end, y + .5f),
360 QPointF(end - cap_width, bottom),
361 QPointF(start + cap_width, bottom)
362 };
363
364 p.drawConvexPolygon(pts, countof(pts));
365
366 if (annotations.empty())
367 return;
368
369 QRectF rect(start + cap_width, y - h / 2,
370 end - start - cap_width * 2, h);
371 p.setPen(text_color);
372
373 // Try to find an annotation that will fit
374 QString best_annotation;
375 int best_width = 0;
376
377 BOOST_FOREACH(const QString &a, annotations) {
378 const int w = p.boundingRect(QRectF(), 0, a).width();
379 if (w <= rect.width() && w > best_width)
380 best_annotation = a, best_width = w;
381 }
382
383 if (best_annotation.isEmpty())
384 best_annotation = annotations.back();
385
386 // If not ellide the last in the list
387 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
388 best_annotation, Qt::ElideRight, rect.width()));
389}
390
391void DecodeTrace::draw_error(QPainter &p, const QString &message,
392 int left, int right)
393{
394 const int y = get_y();
395
396 p.setPen(ErrorBgColour.darker());
397 p.setBrush(ErrorBgColour);
398
399 const QRectF bounding_rect =
400 QRectF(left, INT_MIN / 2 + y, right - left, INT_MAX);
401 const QRectF text_rect = p.boundingRect(bounding_rect,
402 Qt::AlignCenter, message);
403 const float r = text_rect.height() / 4;
404
405 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
406 Qt::AbsoluteSize);
407
408 p.setPen(get_text_colour());
409 p.drawText(text_rect, message);
410}
411
412void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
413 int right, double samples_per_pixel, double pixels_offset)
414{
415 using namespace pv::data;
416 using pv::data::decode::Decoder;
417
418 assert(_decoder_stack);
419
420 shared_ptr<Logic> data;
421 shared_ptr<LogicSignal> logic_signal;
422
423 const list< shared_ptr<Decoder> > &stack = _decoder_stack->stack();
424
425 // We get the logic data of the first probe in the list.
426 // This works because we are currently assuming all
427 // LogicSignals have the same data/snapshot
428 BOOST_FOREACH (const shared_ptr<Decoder> &dec, stack)
429 if (dec && !dec->probes().empty() &&
430 ((logic_signal = (*dec->probes().begin()).second)) &&
431 ((data = logic_signal->logic_data())))
432 break;
433
434 if (!data || data->get_snapshots().empty())
435 return;
436
437 const shared_ptr<LogicSnapshot> snapshot =
438 data->get_snapshots().front();
439 assert(snapshot);
440 const int64_t sample_count = (int64_t)snapshot->get_sample_count();
441 if (sample_count == 0)
442 return;
443
444 const int64_t samples_decoded = _decoder_stack->samples_decoded();
445 if (sample_count == samples_decoded)
446 return;
447
448 const int y = get_y();
449 const double start = max(samples_decoded /
450 samples_per_pixel - pixels_offset, left - 1.0);
451 const double end = min(sample_count / samples_per_pixel -
452 pixels_offset, right + 1.0);
453 const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
454
455 p.setPen(QPen(Qt::NoPen));
456 p.setBrush(Qt::white);
457 p.drawRect(no_decode_rect);
458
459 p.setPen(NoDecodeColour);
460 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
461 p.drawRect(no_decode_rect);
462}
463
464void DecodeTrace::create_decoder_form(int index,
465 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
466 QFormLayout *form)
467{
468 const GSList *probe;
469
470 assert(dec);
471 const srd_decoder *const decoder = dec->decoder();
472 assert(decoder);
473
474 pv::widgets::DecoderGroupBox *const group =
475 new pv::widgets::DecoderGroupBox(
476 QString::fromUtf8(decoder->name));
477 group->set_decoder_visible(dec->shown());
478
479 _delete_mapper.setMapping(group, index);
480 connect(group, SIGNAL(delete_decoder()), &_delete_mapper, SLOT(map()));
481
482 _show_hide_mapper.setMapping(group, index);
483 connect(group, SIGNAL(show_hide_decoder()),
484 &_show_hide_mapper, SLOT(map()));
485
486 QFormLayout *const decoder_form = new QFormLayout;
487 group->add_layout(decoder_form);
488
489 // Add the mandatory probes
490 for(probe = decoder->probes; probe; probe = probe->next) {
491 const struct srd_probe *const p =
492 (struct srd_probe *)probe->data;
493 QComboBox *const combo = create_probe_selector(parent, dec, p);
494 connect(combo, SIGNAL(currentIndexChanged(int)),
495 this, SLOT(on_probe_selected(int)));
496 decoder_form->addRow(tr("<b>%1</b> (%2) *")
497 .arg(QString::fromUtf8(p->name))
498 .arg(QString::fromUtf8(p->desc)), combo);
499
500 const ProbeSelector s = {combo, dec, p};
501 _probe_selectors.push_back(s);
502 }
503
504 // Add the optional probes
505 for(probe = decoder->opt_probes; probe; probe = probe->next) {
506 const struct srd_probe *const p =
507 (struct srd_probe *)probe->data;
508 QComboBox *const combo = create_probe_selector(parent, dec, p);
509 connect(combo, SIGNAL(currentIndexChanged(int)),
510 this, SLOT(on_probe_selected(int)));
511 decoder_form->addRow(tr("<b>%1</b> (%2)")
512 .arg(QString::fromUtf8(p->name))
513 .arg(QString::fromUtf8(p->desc)), combo);
514
515 const ProbeSelector s = {combo, dec, p};
516 _probe_selectors.push_back(s);
517 }
518
519 // Add the options
520 shared_ptr<prop::binding::DecoderOptions> binding(
521 new prop::binding::DecoderOptions(_decoder_stack, dec));
522 binding->add_properties_to_form(decoder_form, true);
523
524 _bindings.push_back(binding);
525
526 form->addRow(group);
527 _decoder_forms.push_back(group);
528}
529
530QComboBox* DecodeTrace::create_probe_selector(
531 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
532 const srd_probe *const probe)
533{
534 assert(dec);
535
536 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
537
538 assert(_decoder_stack);
539 const map<const srd_probe*,
540 shared_ptr<LogicSignal> >::const_iterator probe_iter =
541 dec->probes().find(probe);
542
543 QComboBox *selector = new QComboBox(parent);
544
545 selector->addItem("-", qVariantFromValue((void*)NULL));
546
547 if (probe_iter == dec->probes().end())
548 selector->setCurrentIndex(0);
549
550 for(size_t i = 0; i < sigs.size(); i++) {
551 const shared_ptr<view::Signal> s(sigs[i]);
552 assert(s);
553
554 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
555 {
556 selector->addItem(s->get_name(),
557 qVariantFromValue((void*)s.get()));
558 if ((*probe_iter).second == s)
559 selector->setCurrentIndex(i + 1);
560 }
561 }
562
563 return selector;
564}
565
566void DecodeTrace::commit_decoder_probes(shared_ptr<data::decode::Decoder> &dec)
567{
568 assert(dec);
569
570 map<const srd_probe*, shared_ptr<LogicSignal> > probe_map;
571 const vector< shared_ptr<Signal> > sigs = _session.get_signals();
572
573 BOOST_FOREACH(const ProbeSelector &s, _probe_selectors)
574 {
575 if(s._decoder != dec)
576 break;
577
578 const LogicSignal *const selection =
579 (LogicSignal*)s._combo->itemData(
580 s._combo->currentIndex()).value<void*>();
581
582 BOOST_FOREACH(shared_ptr<Signal> sig, sigs)
583 if(sig.get() == selection) {
584 probe_map[s._probe] =
585 dynamic_pointer_cast<LogicSignal>(sig);
586 break;
587 }
588 }
589
590 dec->set_probes(probe_map);
591}
592
593void DecodeTrace::commit_probes()
594{
595 assert(_decoder_stack);
596 BOOST_FOREACH(shared_ptr<data::decode::Decoder> dec,
597 _decoder_stack->stack())
598 commit_decoder_probes(dec);
599
600 _decoder_stack->begin_decode();
601}
602
603void DecodeTrace::on_new_decode_data()
604{
605 if (_view)
606 _view->update_viewport();
607}
608
609void DecodeTrace::delete_pressed()
610{
611 on_delete();
612}
613
614void DecodeTrace::on_delete()
615{
616 _session.remove_decode_signal(this);
617}
618
619void DecodeTrace::on_probe_selected(int)
620{
621 commit_probes();
622}
623
624void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
625{
626 assert(decoder);
627 assert(_decoder_stack);
628 _decoder_stack->push(shared_ptr<data::decode::Decoder>(
629 new data::decode::Decoder(decoder)));
630 _decoder_stack->begin_decode();
631
632 create_popup_form();
633}
634
635void DecodeTrace::on_delete_decoder(int index)
636{
637 _decoder_stack->remove(index);
638
639 // Update the popup
640 create_popup_form();
641
642 _decoder_stack->begin_decode();
643}
644
645void DecodeTrace::on_show_hide_decoder(int index)
646{
647 using pv::data::decode::Decoder;
648
649 const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
650
651 // Find the decoder in the stack
652 list< shared_ptr<Decoder> >::const_iterator iter = stack.begin();
653 for(int i = 0; i < index; i++, iter++)
654 assert(iter != stack.end());
655
656 shared_ptr<Decoder> dec = *iter;
657 assert(dec);
658
659 const bool show = !dec->shown();
660 dec->show(show);
661
662 assert(index < (int)_decoder_forms.size());
663 _decoder_forms[index]->set_decoder_visible(show);
664
665 _view->update_viewport();
666}
667
668} // namespace view
669} // namespace pv