]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
Added some missing fromUtf8s
[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(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
530 QComboBox* 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
566 void 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
593 void 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
603 void DecodeTrace::on_new_decode_data()
604 {
605         if (_view)
606                 _view->update_viewport();
607 }
608
609 void DecodeTrace::delete_pressed()
610 {
611         on_delete();
612 }
613
614 void DecodeTrace::on_delete()
615 {
616         _session.remove_decode_signal(this);
617 }
618
619 void DecodeTrace::on_probe_selected(int)
620 {
621         commit_probes();
622 }
623
624 void 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
635 void 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
645 void 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