]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
2ec84481b407c847996ca3c69e22075997b883c6
[pulseview.git] / pv / view / decodetrace.cpp
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
21 extern "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
50 using boost::dynamic_pointer_cast;
51 using boost::shared_ptr;
52 using std::list;
53 using std::max;
54 using std::map;
55 using std::min;
56 using std::vector;
57
58 namespace pv {
59 namespace view {
60
61 const 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
68 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
69 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
70
71 const double DecodeTrace::EndCapWidth = 5;
72 const int DecodeTrace::DrawPadding = 100;
73
74 const 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
93 const 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
112 DecodeTrace::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
132 bool DecodeTrace::enabled() const
133 {
134         return true;
135 }
136
137 const boost::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
138 {
139         return _decoder_stack;
140 }
141
142 void DecodeTrace::set_view(pv::view::View *view)
143 {
144         assert(view);
145         Trace::set_view(view);
146 }
147
148 void 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
154 void 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_error(p, err, left, right);
186                 draw_unresolved_period(p, annotation_height, left, right,
187                         samples_per_pixel, pixels_offset);
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
226 void 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
279 QMenu* 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
293 void 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
318 void 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
335 void 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
391 void 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
412 void 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
464 void 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(p->name).arg(p->desc), combo);
498
499                 const ProbeSelector s = {combo, dec, p};
500                 _probe_selectors.push_back(s);
501         }
502
503         // Add the optional probes
504         for(probe = decoder->opt_probes; probe; probe = probe->next) {
505                 const struct srd_probe *const p =
506                         (struct srd_probe *)probe->data;
507                 QComboBox *const combo = create_probe_selector(parent, dec, p);
508                 connect(combo, SIGNAL(currentIndexChanged(int)),
509                         this, SLOT(on_probe_selected(int)));
510                 decoder_form->addRow(tr("<b>%1</b> (%2)")
511                         .arg(p->name).arg(p->desc), combo);
512
513                 const ProbeSelector s = {combo, dec, p};
514                 _probe_selectors.push_back(s);
515         }
516
517         // Add the options
518         shared_ptr<prop::binding::DecoderOptions> binding(
519                 new prop::binding::DecoderOptions(_decoder_stack, dec));
520         binding->add_properties_to_form(decoder_form, true);
521
522         _bindings.push_back(binding);
523
524         form->addRow(group);
525         _decoder_forms.push_back(group);
526 }
527
528 QComboBox* DecodeTrace::create_probe_selector(
529         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
530         const srd_probe *const probe)
531 {
532         assert(dec);
533
534         const vector< shared_ptr<Signal> > sigs = _session.get_signals();
535
536         assert(_decoder_stack);
537         const map<const srd_probe*,
538                 shared_ptr<LogicSignal> >::const_iterator probe_iter =
539                 dec->probes().find(probe);
540
541         QComboBox *selector = new QComboBox(parent);
542
543         selector->addItem("-", qVariantFromValue((void*)NULL));
544
545         if (probe_iter == dec->probes().end())
546                 selector->setCurrentIndex(0);
547
548         for(size_t i = 0; i < sigs.size(); i++) {
549                 const shared_ptr<view::Signal> s(sigs[i]);
550                 assert(s);
551
552                 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled())
553                 {
554                         selector->addItem(s->get_name(),
555                                 qVariantFromValue((void*)s.get()));
556                         if ((*probe_iter).second == s)
557                                 selector->setCurrentIndex(i + 1);
558                 }
559         }
560
561         return selector;
562 }
563
564 void DecodeTrace::commit_decoder_probes(shared_ptr<data::decode::Decoder> &dec)
565 {
566         assert(dec);
567
568         map<const srd_probe*, shared_ptr<LogicSignal> > probe_map;
569         const vector< shared_ptr<Signal> > sigs = _session.get_signals();
570
571         BOOST_FOREACH(const ProbeSelector &s, _probe_selectors)
572         {
573                 if(s._decoder != dec)
574                         break;
575
576                 const LogicSignal *const selection =
577                         (LogicSignal*)s._combo->itemData(
578                                 s._combo->currentIndex()).value<void*>();
579
580                 BOOST_FOREACH(shared_ptr<Signal> sig, sigs)
581                         if(sig.get() == selection) {
582                                 probe_map[s._probe] =
583                                         dynamic_pointer_cast<LogicSignal>(sig);
584                                 break;
585                         }
586         }
587
588         dec->set_probes(probe_map);
589 }
590
591 void DecodeTrace::commit_probes()
592 {
593         assert(_decoder_stack);
594         BOOST_FOREACH(shared_ptr<data::decode::Decoder> dec,
595                 _decoder_stack->stack())
596                 commit_decoder_probes(dec);
597
598         _decoder_stack->begin_decode();
599 }
600
601 void DecodeTrace::on_new_decode_data()
602 {
603         if (_view)
604                 _view->update_viewport();
605 }
606
607 void DecodeTrace::delete_pressed()
608 {
609         on_delete();
610 }
611
612 void DecodeTrace::on_delete()
613 {
614         _session.remove_decode_signal(this);
615 }
616
617 void DecodeTrace::on_probe_selected(int)
618 {
619         commit_probes();
620 }
621
622 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
623 {
624         assert(decoder);
625         assert(_decoder_stack);
626         _decoder_stack->push(shared_ptr<data::decode::Decoder>(
627                 new data::decode::Decoder(decoder)));
628         _decoder_stack->begin_decode();
629
630         create_popup_form();
631 }
632
633 void DecodeTrace::on_delete_decoder(int index)
634 {
635         _decoder_stack->remove(index);
636
637         // Update the popup
638         create_popup_form();    
639
640         _decoder_stack->begin_decode();
641 }
642
643 void DecodeTrace::on_show_hide_decoder(int index)
644 {
645         using pv::data::decode::Decoder;
646
647         const list< shared_ptr<Decoder> > stack(_decoder_stack->stack());
648
649         // Find the decoder in the stack
650         list< shared_ptr<Decoder> >::const_iterator iter = stack.begin();
651         for(int i = 0; i < index; i++, iter++)
652                 assert(iter != stack.end());
653
654         shared_ptr<Decoder> dec = *iter;
655         assert(dec);
656
657         const bool show = !dec->shown();
658         dec->show(show);
659
660         assert(index < (int)_decoder_forms.size());
661         _decoder_forms[index]->set_decoder_visible(show);
662
663         _view->update_viewport();
664 }
665
666 } // namespace view
667 } // namespace pv