]> sigrok.org Git - pulseview.git/blame_incremental - pv/view/decodetrace.cpp
Improved decode annotation colouring
[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_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
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(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
528QComboBox* 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
564void 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
591void 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
601void DecodeTrace::on_new_decode_data()
602{
603 if (_view)
604 _view->update_viewport();
605}
606
607void DecodeTrace::delete_pressed()
608{
609 on_delete();
610}
611
612void DecodeTrace::on_delete()
613{
614 _session.remove_decode_signal(this);
615}
616
617void DecodeTrace::on_probe_selected(int)
618{
619 commit_probes();
620}
621
622void 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
633void 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
643void 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