]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
View: Honor ENABLE_DECODE compiler switch
[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 <mutex>
26
27 #include <extdef.h>
28
29 #include <tuple>
30
31 #include <boost/functional/hash.hpp>
32 #include <boost/thread/locks.hpp>
33 #include <boost/thread/shared_mutex.hpp>
34
35 #include <QAction>
36 #include <QApplication>
37 #include <QComboBox>
38 #include <QFormLayout>
39 #include <QLabel>
40 #include <QMenu>
41 #include <QPushButton>
42 #include <QToolTip>
43
44 #include "decodetrace.hpp"
45
46 #include <pv/session.hpp>
47 #include <pv/data/decoderstack.hpp>
48 #include <pv/data/decode/decoder.hpp>
49 #include <pv/data/logic.hpp>
50 #include <pv/data/logicsegment.hpp>
51 #include <pv/data/decode/annotation.hpp>
52 #include <pv/view/logicsignal.hpp>
53 #include <pv/view/view.hpp>
54 #include <pv/view/viewport.hpp>
55 #include <pv/widgets/decodergroupbox.hpp>
56 #include <pv/widgets/decodermenu.hpp>
57
58 using boost::shared_lock;
59 using boost::shared_mutex;
60 using std::dynamic_pointer_cast;
61 using std::list;
62 using std::lock_guard;
63 using std::make_pair;
64 using std::max;
65 using std::make_pair;
66 using std::map;
67 using std::min;
68 using std::pair;
69 using std::shared_ptr;
70 using std::tie;
71 using std::unordered_set;
72 using std::vector;
73
74 namespace pv {
75 namespace view {
76
77 const QColor DecodeTrace::DecodeColours[4] = {
78         QColor(0xEF, 0x29, 0x29),       // Red
79         QColor(0xFC, 0xE9, 0x4F),       // Yellow
80         QColor(0x8A, 0xE2, 0x34),       // Green
81         QColor(0x72, 0x9F, 0xCF)        // Blue
82 };
83
84 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
85 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
86
87 const int DecodeTrace::ArrowSize = 4;
88 const double DecodeTrace::EndCapWidth = 5;
89 const int DecodeTrace::DrawPadding = 100;
90
91 const QColor DecodeTrace::Colours[16] = {
92         QColor(0xEF, 0x29, 0x29),
93         QColor(0xF6, 0x6A, 0x32),
94         QColor(0xFC, 0xAE, 0x3E),
95         QColor(0xFB, 0xCA, 0x47),
96         QColor(0xFC, 0xE9, 0x4F),
97         QColor(0xCD, 0xF0, 0x40),
98         QColor(0x8A, 0xE2, 0x34),
99         QColor(0x4E, 0xDC, 0x44),
100         QColor(0x55, 0xD7, 0x95),
101         QColor(0x64, 0xD1, 0xD2),
102         QColor(0x72, 0x9F, 0xCF),
103         QColor(0xD4, 0x76, 0xC4),
104         QColor(0x9D, 0x79, 0xB9),
105         QColor(0xAD, 0x7F, 0xA8),
106         QColor(0xC2, 0x62, 0x9B),
107         QColor(0xD7, 0x47, 0x6F)
108 };
109
110 const QColor DecodeTrace::OutlineColours[16] = {
111         QColor(0x77, 0x14, 0x14),
112         QColor(0x7B, 0x35, 0x19),
113         QColor(0x7E, 0x57, 0x1F),
114         QColor(0x7D, 0x65, 0x23),
115         QColor(0x7E, 0x74, 0x27),
116         QColor(0x66, 0x78, 0x20),
117         QColor(0x45, 0x71, 0x1A),
118         QColor(0x27, 0x6E, 0x22),
119         QColor(0x2A, 0x6B, 0x4A),
120         QColor(0x32, 0x68, 0x69),
121         QColor(0x39, 0x4F, 0x67),
122         QColor(0x6A, 0x3B, 0x62),
123         QColor(0x4E, 0x3C, 0x5C),
124         QColor(0x56, 0x3F, 0x54),
125         QColor(0x61, 0x31, 0x4D),
126         QColor(0x6B, 0x23, 0x37)
127 };
128
129 DecodeTrace::DecodeTrace(pv::Session &session,
130         std::shared_ptr<pv::data::DecoderStack> decoder_stack, int index) :
131         Trace(QString::fromUtf8(
132                 decoder_stack->stack().front()->decoder()->name)),
133         session_(session),
134         decoder_stack_(decoder_stack),
135         row_height_(0),
136         max_visible_rows_(0),
137         delete_mapper_(this),
138         show_hide_mapper_(this)
139 {
140         assert(decoder_stack_);
141
142         set_colour(DecodeColours[index % countof(DecodeColours)]);
143
144         connect(decoder_stack_.get(), SIGNAL(new_decode_data()),
145                 this, SLOT(on_new_decode_data()));
146         connect(&delete_mapper_, SIGNAL(mapped(int)),
147                 this, SLOT(on_delete_decoder(int)));
148         connect(&show_hide_mapper_, SIGNAL(mapped(int)),
149                 this, SLOT(on_show_hide_decoder(int)));
150 }
151
152 bool DecodeTrace::enabled() const
153 {
154         return true;
155 }
156
157 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
158 {
159         return decoder_stack_;
160 }
161
162 pair<int, int> DecodeTrace::v_extents() const
163 {
164         const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
165
166         return make_pair(-row_height, row_height * max_visible_rows_);
167 }
168
169 void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
170 {
171         Trace::paint_back(p, pp);
172         paint_axis(p, pp, get_visual_y());
173 }
174
175 void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
176 {
177         using namespace pv::data::decode;
178
179         const int text_height = ViewItemPaintParams::text_height();
180         row_height_ = (text_height * 6) / 4;
181         const int annotation_height = (text_height * 5) / 4;
182
183         assert(decoder_stack_);
184         const QString err = decoder_stack_->error_message();
185         if (!err.isEmpty()) {
186                 draw_unresolved_period(
187                         p, annotation_height, pp.left(), pp.right());
188                 draw_error(p, err, pp);
189                 return;
190         }
191
192         // Iterate through the rows
193         int y = get_visual_y();
194         pair<uint64_t, uint64_t> sample_range = get_sample_range(
195                 pp.left(), pp.right());
196
197         assert(decoder_stack_);
198         const vector<Row> rows(decoder_stack_->get_visible_rows());
199
200         visible_rows_.clear();
201         for (size_t i = 0; i < rows.size(); i++) {
202                 const Row &row = rows[i];
203
204                 size_t base_colour = 0x13579BDF;
205                 boost::hash_combine(base_colour, this);
206                 boost::hash_combine(base_colour, row.decoder());
207                 boost::hash_combine(base_colour, row.row());
208                 base_colour >>= 16;
209
210                 vector<Annotation> annotations;
211                 decoder_stack_->get_annotation_subset(annotations, row,
212                         sample_range.first, sample_range.second);
213                 if (!annotations.empty()) {
214                         draw_annotations(annotations, p, annotation_height, pp, y,
215                                 base_colour);
216
217                         y += row_height_;
218
219                         visible_rows_.push_back(rows[i]);
220                 }
221         }
222
223         // Draw the hatching
224         draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
225
226         // Update the maximum row count if needed
227         max_visible_rows_ = std::max(max_visible_rows_, (int)visible_rows_.size());
228 }
229
230 void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
231 {
232         using namespace pv::data::decode;
233
234         assert(row_height_);
235
236         for (size_t i = 0; i < visible_rows_.size(); i++) {
237                 const int y = i * row_height_ + get_visual_y();
238
239                 p.setPen(QPen(Qt::NoPen));
240                 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
241
242                 if (i != 0) {
243                         const QPointF points[] = {
244                                 QPointF(pp.left(), y - ArrowSize),
245                                 QPointF(pp.left() + ArrowSize, y),
246                                 QPointF(pp.left(), y + ArrowSize)
247                         };
248                         p.drawPolygon(points, countof(points));
249                 }
250
251                 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
252                         pp.right() - pp.left(), row_height_);
253                 const QString h(visible_rows_[i].title());
254                 const int f = Qt::AlignLeft | Qt::AlignVCenter |
255                         Qt::TextDontClip;
256
257                 // Draw the outline
258                 p.setPen(QApplication::palette().color(QPalette::Base));
259                 for (int dx = -1; dx <= 1; dx++)
260                         for (int dy = -1; dy <= 1; dy++)
261                                 if (dx != 0 && dy != 0)
262                                         p.drawText(r.translated(dx, dy), f, h);
263
264                 // Draw the text
265                 p.setPen(QApplication::palette().color(QPalette::WindowText));
266                 p.drawText(r, f, h);
267         }
268 }
269
270 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
271 {
272         using pv::data::decode::Decoder;
273
274         assert(form);
275         assert(parent);
276         assert(decoder_stack_);
277
278         // Add the standard options
279         Trace::populate_popup_form(parent, form);
280
281         // Add the decoder options
282         bindings_.clear();
283         channel_selectors_.clear();
284         decoder_forms_.clear();
285
286         const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
287
288         if (stack.empty()) {
289                 QLabel *const l = new QLabel(
290                         tr("<p><i>No decoders in the stack</i></p>"));
291                 l->setAlignment(Qt::AlignCenter);
292                 form->addRow(l);
293         } else {
294                 auto iter = stack.cbegin();
295                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
296                         shared_ptr<Decoder> dec(*iter);
297                         create_decoder_form(i, dec, parent, form);
298                 }
299
300                 form->addRow(new QLabel(
301                         tr("<i>* Required channels</i>"), parent));
302         }
303
304         // Add stacking button
305         pv::widgets::DecoderMenu *const decoder_menu =
306                 new pv::widgets::DecoderMenu(parent);
307         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
308                 this, SLOT(on_stack_decoder(srd_decoder*)));
309
310         QPushButton *const stack_button =
311                 new QPushButton(tr("Stack Decoder"), parent);
312         stack_button->setMenu(decoder_menu);
313
314         QHBoxLayout *stack_button_box = new QHBoxLayout;
315         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
316         form->addRow(stack_button_box);
317 }
318
319 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
320 {
321         QMenu *const menu = Trace::create_context_menu(parent);
322
323         menu->addSeparator();
324
325         QAction *const del = new QAction(tr("Delete"), this);
326         del->setShortcuts(QKeySequence::Delete);
327         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
328         menu->addAction(del);
329
330         return menu;
331 }
332
333 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
334                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
335                 size_t base_colour)
336 {
337         using namespace pv::data::decode;
338
339         vector<Annotation> a_block;
340         int prev_ann_pos = INT_MIN;
341
342         double samples_per_pixel, pixels_offset;
343         tie(pixels_offset, samples_per_pixel) =
344                 get_pixels_offset_samples_per_pixel();
345
346         // Gather all annotations that form a visual "block" and draw them as such
347         for (const Annotation &a : annotations) {
348
349                 const int end = a.end_sample() / samples_per_pixel - pixels_offset;
350                 const int delta = end - prev_ann_pos;
351
352                 // Some annotations are in reverse order, so we cannot
353                 // simply check for delta > 1
354                 if (abs(delta) > 1) {
355                         // Block was broken, draw it
356                         if (a_block.size() == 1)
357                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour);
358                         else
359                                 if (a_block.size() > 0)
360                                         draw_annotation_block(a_block, p, h, y, base_colour);
361
362                         a_block.clear();
363                 }
364
365                 a_block.push_back(a);
366                 prev_ann_pos = end;
367         }
368
369         if (a_block.size() == 1)
370                 draw_annotation(a_block.front(), p, h, pp, y, base_colour);
371         else
372                 draw_annotation_block(a_block, p, h, y, base_colour);
373 }
374
375 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
376         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
377         size_t base_colour) const
378 {
379         double samples_per_pixel, pixels_offset;
380         tie(pixels_offset, samples_per_pixel) =
381                 get_pixels_offset_samples_per_pixel();
382
383         const double start = a.start_sample() / samples_per_pixel -
384                 pixels_offset;
385         const double end = a.end_sample() / samples_per_pixel -
386                 pixels_offset;
387
388         const size_t colour = (base_colour + a.format()) % countof(Colours);
389         const QColor &fill = Colours[colour];
390         const QColor &outline = OutlineColours[colour];
391
392         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
393                 return;
394
395         if (a.start_sample() == a.end_sample())
396                 draw_instant(a, p, fill, outline, h, start, y);
397         else
398                 draw_range(a, p, fill, outline, h, start, end, y);
399 }
400
401 void DecodeTrace::draw_annotation_block(
402         vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
403         int y, size_t base_colour) const
404 {
405         using namespace pv::data::decode;
406
407         double samples_per_pixel, pixels_offset;
408         tie(pixels_offset, samples_per_pixel) =
409                 get_pixels_offset_samples_per_pixel();
410
411         const double start = annotations.front().start_sample() /
412                 samples_per_pixel - pixels_offset;
413         const double end = annotations.back().end_sample() /
414                 samples_per_pixel - pixels_offset;
415
416         const double top = y + .5 - h / 2;
417         const double bottom = y + .5 + h / 2;
418
419         const size_t colour = (base_colour + annotations.front().format()) %
420                 countof(Colours);
421
422         // Check if all annotations are of the same type (i.e. we can use one color)
423         // or if we should use a neutral color (i.e. gray)
424         const int format = annotations.front().format();
425         const bool single_format = std::all_of(
426                 annotations.begin(), annotations.end(),
427                 [&](const Annotation &a) { return a.format() == format; });
428
429         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
430         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
431                 Qt::Dense4Pattern));
432         p.drawRoundedRect(
433                 QRectF(start, top, end - start, bottom - top), h/4, h/4);
434 }
435
436 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
437         QColor fill, QColor outline, int h, double x, int y) const
438 {
439         const QString text = a.annotations().empty() ?
440                 QString() : a.annotations().back();
441         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
442                 0.0) + h;
443         const QRectF rect(x - w / 2, y - h / 2, w, h);
444
445         p.setPen(outline);
446         p.setBrush(fill);
447         p.drawRoundedRect(rect, h / 2, h / 2);
448
449         p.setPen(Qt::black);
450         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
451 }
452
453 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
454         QColor fill, QColor outline, int h, double start,
455         double end, int y) const
456 {
457         const double top = y + .5 - h / 2;
458         const double bottom = y + .5 + h / 2;
459         const vector<QString> annotations = a.annotations();
460
461         p.setPen(outline);
462         p.setBrush(fill);
463
464         // If the two ends are within 1 pixel, draw a vertical line
465         if (start + 1.0 > end) {
466                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
467                 return;
468         }
469
470         const double cap_width = min((end - start) / 4, EndCapWidth);
471
472         QPointF pts[] = {
473                 QPointF(start, y + .5f),
474                 QPointF(start + cap_width, top),
475                 QPointF(end - cap_width, top),
476                 QPointF(end, y + .5f),
477                 QPointF(end - cap_width, bottom),
478                 QPointF(start + cap_width, bottom)
479         };
480
481         p.drawConvexPolygon(pts, countof(pts));
482
483         if (annotations.empty())
484                 return;
485
486         QRectF rect(start + cap_width, y - h / 2,
487                 end - start - cap_width * 2, h);
488         if (rect.width() <= 4)
489                 return;
490
491         p.setPen(Qt::black);
492
493         // Try to find an annotation that will fit
494         QString best_annotation;
495         int best_width = 0;
496
497         for (const QString &a : annotations) {
498                 const int w = p.boundingRect(QRectF(), 0, a).width();
499                 if (w <= rect.width() && w > best_width)
500                         best_annotation = a, best_width = w;
501         }
502
503         if (best_annotation.isEmpty())
504                 best_annotation = annotations.back();
505
506         // If not ellide the last in the list
507         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
508                 best_annotation, Qt::ElideRight, rect.width()));
509 }
510
511 void DecodeTrace::draw_error(QPainter &p, const QString &message,
512         const ViewItemPaintParams &pp)
513 {
514         const int y = get_visual_y();
515
516         p.setPen(ErrorBgColour.darker());
517         p.setBrush(ErrorBgColour);
518
519         const QRectF bounding_rect =
520                 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
521         const QRectF text_rect = p.boundingRect(bounding_rect,
522                 Qt::AlignCenter, message);
523         const float r = text_rect.height() / 4;
524
525         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
526                 Qt::AbsoluteSize);
527
528         p.setPen(Qt::black);
529         p.drawText(text_rect, message);
530 }
531
532 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
533         int right) const
534 {
535         using namespace pv::data;
536         using pv::data::decode::Decoder;
537
538         double samples_per_pixel, pixels_offset;
539
540         assert(decoder_stack_); 
541
542         shared_ptr<Logic> data;
543         shared_ptr<LogicSignal> logic_signal;
544
545         const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
546
547         // We get the logic data of the first channel in the list.
548         // This works because we are currently assuming all
549         // LogicSignals have the same data/segment
550         for (const shared_ptr<Decoder> &dec : stack)
551                 if (dec && !dec->channels().empty() &&
552                         ((logic_signal = (*dec->channels().begin()).second)) &&
553                         ((data = logic_signal->logic_data())))
554                         break;
555
556         if (!data || data->logic_segments().empty())
557                 return;
558
559         const shared_ptr<LogicSegment> segment =
560                 data->logic_segments().front();
561         assert(segment);
562         const int64_t sample_count = (int64_t)segment->get_sample_count();
563         if (sample_count == 0)
564                 return;
565
566         const int64_t samples_decoded = decoder_stack_->samples_decoded();
567         if (sample_count == samples_decoded)
568                 return;
569
570         const int y = get_visual_y();
571
572         tie(pixels_offset, samples_per_pixel) =
573                 get_pixels_offset_samples_per_pixel();
574
575         const double start = max(samples_decoded /
576                 samples_per_pixel - pixels_offset, left - 1.0);
577         const double end = min(sample_count / samples_per_pixel -
578                 pixels_offset, right + 1.0);
579         const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
580
581         p.setPen(QPen(Qt::NoPen));
582         p.setBrush(Qt::white);
583         p.drawRect(no_decode_rect);
584
585         p.setPen(NoDecodeColour);
586         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
587         p.drawRect(no_decode_rect);
588 }
589
590 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
591 {
592         assert(owner_);
593         assert(decoder_stack_);
594
595         const View *view = owner_->view();
596         assert(view);
597
598         const double scale = view->scale();
599         assert(scale > 0);
600
601         const double pixels_offset =
602                 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
603
604         double samplerate = decoder_stack_->samplerate();
605
606         // Show sample rate as 1Hz when it is unknown
607         if (samplerate == 0.0)
608                 samplerate = 1.0;
609
610         return make_pair(pixels_offset, samplerate * scale);
611 }
612
613 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
614         int x_start, int x_end) const
615 {
616         double samples_per_pixel, pixels_offset;
617         tie(pixels_offset, samples_per_pixel) =
618                 get_pixels_offset_samples_per_pixel();
619
620         const uint64_t start = (uint64_t)max(
621                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
622         const uint64_t end = (uint64_t)max(
623                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
624
625         return make_pair(start, end);
626 }
627
628 int DecodeTrace::get_row_at_point(const QPoint &point)
629 {
630         if (!row_height_)
631                 return -1;
632
633         const int y = (point.y() - get_visual_y() + row_height_ / 2);
634
635         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
636         if (y < 0)
637                 return -1;
638
639         const int row = y / row_height_;
640
641         if (row >= (int)visible_rows_.size())
642                 return -1;
643
644         return row;
645 }
646
647 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
648 {
649         using namespace pv::data::decode;
650
651         if (!enabled())
652                 return QString();
653
654         const pair<uint64_t, uint64_t> sample_range =
655                 get_sample_range(point.x(), point.x() + 1);
656         const int row = get_row_at_point(point);
657         if (row < 0)
658                 return QString();
659
660         vector<pv::data::decode::Annotation> annotations;
661
662         assert(decoder_stack_);
663         decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
664                 sample_range.first, sample_range.second);
665
666         return (annotations.empty()) ?
667                 QString() : annotations[0].annotations().front();
668 }
669
670 void DecodeTrace::hover_point_changed()
671 {
672         assert(owner_);
673
674         const View *const view = owner_->view();
675         assert(view);
676
677         QPoint hp = view->hover_point();
678         QString ann = get_annotation_at_point(hp);
679
680         assert(view);
681
682         if (!row_height_ || ann.isEmpty()) {
683                 QToolTip::hideText();
684                 return;
685         }
686
687         const int hover_row = get_row_at_point(hp);
688
689         QFontMetrics m(QToolTip::font());
690         const QRect text_size = m.boundingRect(QRect(), 0, ann);
691
692         // This is OS-specific and unfortunately we can't query it, so
693         // use an approximation to at least try to minimize the error.
694         const int padding = 8;
695
696         // Make sure the tool tip doesn't overlap with the mouse cursor.
697         // If it did, the tool tip would constantly hide and re-appear.
698         // We also push it up by one row so that it appears above the
699         // decode trace, not below.
700         hp.setX(hp.x() - (text_size.width() / 2) - padding);
701
702         hp.setY(get_visual_y() - (row_height_ / 2) +
703                 (hover_row * row_height_) -
704                 row_height_ - text_size.height() - padding);
705
706         QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
707 }
708
709 void DecodeTrace::create_decoder_form(int index,
710         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
711         QFormLayout *form)
712 {
713         const GSList *l;
714
715         assert(dec);
716         const srd_decoder *const decoder = dec->decoder();
717         assert(decoder);
718
719         const bool decoder_deletable = index > 0;
720
721         pv::widgets::DecoderGroupBox *const group =
722                 new pv::widgets::DecoderGroupBox(
723                         QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
724         group->set_decoder_visible(dec->shown());
725
726         if (decoder_deletable) {
727                 delete_mapper_.setMapping(group, index);
728                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
729         }
730
731         show_hide_mapper_.setMapping(group, index);
732         connect(group, SIGNAL(show_hide_decoder()),
733                 &show_hide_mapper_, SLOT(map()));
734
735         QFormLayout *const decoder_form = new QFormLayout;
736         group->add_layout(decoder_form);
737
738         // Add the mandatory channels
739         for (l = decoder->channels; l; l = l->next) {
740                 const struct srd_channel *const pdch =
741                         (struct srd_channel *)l->data;
742                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
743                 connect(combo, SIGNAL(currentIndexChanged(int)),
744                         this, SLOT(on_channel_selected(int)));
745                 decoder_form->addRow(tr("<b>%1</b> (%2) *")
746                         .arg(QString::fromUtf8(pdch->name))
747                         .arg(QString::fromUtf8(pdch->desc)), combo);
748
749                 const ChannelSelector s = {combo, dec, pdch};
750                 channel_selectors_.push_back(s);
751         }
752
753         // Add the optional channels
754         for (l = decoder->opt_channels; l; l = l->next) {
755                 const struct srd_channel *const pdch =
756                         (struct srd_channel *)l->data;
757                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
758                 connect(combo, SIGNAL(currentIndexChanged(int)),
759                         this, SLOT(on_channel_selected(int)));
760                 decoder_form->addRow(tr("<b>%1</b> (%2)")
761                         .arg(QString::fromUtf8(pdch->name))
762                         .arg(QString::fromUtf8(pdch->desc)), combo);
763
764                 const ChannelSelector s = {combo, dec, pdch};
765                 channel_selectors_.push_back(s);
766         }
767
768         // Add the options
769         shared_ptr<binding::Decoder> binding(
770                 new binding::Decoder(decoder_stack_, dec));
771         binding->add_properties_to_form(decoder_form, true);
772
773         bindings_.push_back(binding);
774
775         form->addRow(group);
776         decoder_forms_.push_back(group);
777 }
778
779 QComboBox* DecodeTrace::create_channel_selector(
780         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
781         const srd_channel *const pdch)
782 {
783         assert(dec);
784
785         const auto sigs(session_.signals());
786
787         vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
788         std::sort(sig_list.begin(), sig_list.end(),
789                 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
790                         return a->name().compare(b->name()) < 0; });
791
792         assert(decoder_stack_);
793         const auto channel_iter = dec->channels().find(pdch);
794
795         QComboBox *selector = new QComboBox(parent);
796
797         selector->addItem("-", qVariantFromValue((void*)nullptr));
798
799         if (channel_iter == dec->channels().end())
800                 selector->setCurrentIndex(0);
801
802         for (const shared_ptr<view::Signal> &s : sig_list) {
803                 assert(s);
804                 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled()) {
805                         selector->addItem(s->name(),
806                                 qVariantFromValue((void*)s.get()));
807
808                         if (channel_iter != dec->channels().end() &&
809                                 (*channel_iter).second == s)
810                                 selector->setCurrentIndex(
811                                         selector->count() - 1);
812                 }
813         }
814
815         return selector;
816 }
817
818 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
819 {
820         assert(dec);
821
822         map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
823
824         const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
825
826         for (const ChannelSelector &s : channel_selectors_) {
827                 if (s.decoder_ != dec)
828                         break;
829
830                 const LogicSignal *const selection =
831                         (LogicSignal*)s.combo_->itemData(
832                                 s.combo_->currentIndex()).value<void*>();
833
834                 for (shared_ptr<Signal> sig : sigs)
835                         if (sig.get() == selection) {
836                                 channel_map[s.pdch_] =
837                                         dynamic_pointer_cast<LogicSignal>(sig);
838                                 break;
839                         }
840         }
841
842         dec->set_channels(channel_map);
843 }
844
845 void DecodeTrace::commit_channels()
846 {
847         assert(decoder_stack_);
848         for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
849                 commit_decoder_channels(dec);
850
851         decoder_stack_->begin_decode();
852 }
853
854 void DecodeTrace::on_new_decode_data()
855 {
856         if (owner_)
857                 owner_->row_item_appearance_changed(false, true);
858 }
859
860 void DecodeTrace::delete_pressed()
861 {
862         on_delete();
863 }
864
865 void DecodeTrace::on_delete()
866 {
867         session_.remove_decode_signal(this);
868 }
869
870 void DecodeTrace::on_channel_selected(int)
871 {
872         commit_channels();
873 }
874
875 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
876 {
877         assert(decoder);
878         assert(decoder_stack_);
879         decoder_stack_->push(shared_ptr<data::decode::Decoder>(
880                 new data::decode::Decoder(decoder)));
881         decoder_stack_->begin_decode();
882
883         create_popup_form();
884 }
885
886 void DecodeTrace::on_delete_decoder(int index)
887 {
888         decoder_stack_->remove(index);
889
890         // Update the popup
891         create_popup_form();    
892
893         decoder_stack_->begin_decode();
894 }
895
896 void DecodeTrace::on_show_hide_decoder(int index)
897 {
898         using pv::data::decode::Decoder;
899
900         const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
901
902         // Find the decoder in the stack
903         auto iter = stack.cbegin();
904         for (int i = 0; i < index; i++, iter++)
905                 assert(iter != stack.end());
906
907         shared_ptr<Decoder> dec = *iter;
908         assert(dec);
909
910         const bool show = !dec->shown();
911         dec->show(show);
912
913         assert(index < (int)decoder_forms_.size());
914         decoder_forms_[index]->set_decoder_visible(show);
915
916         if (owner_)
917                 owner_->row_item_appearance_changed(false, true);
918 }
919
920 } // namespace view
921 } // namespace pv