]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
DecodeTrace: Use an stl algorithm to find if all annotations are the same colour
[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         const double cap_width = min((end - start) / 4, EndCapWidth);
419
420         QPointF pts[] = {
421                 QPointF(start, y + .5f),
422                 QPointF(start + cap_width, top),
423                 QPointF(end - cap_width, top),
424                 QPointF(end, y + .5f),
425                 QPointF(end - cap_width, bottom),
426                 QPointF(start + cap_width, bottom)
427         };
428
429         const size_t colour = (base_colour + annotations.front().format()) %
430                 countof(Colours);
431
432         // Check if all annotations are of the same type (i.e. we can use one color)
433         // or if we should use a neutral color (i.e. gray)
434         const int format = annotations.front().format();
435         const bool single_format = std::all_of(
436                 annotations.begin(), annotations.end(),
437                 [&](const Annotation &a) { return a.format() == format; });
438
439         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
440         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
441                 Qt::Dense4Pattern));
442
443         p.drawConvexPolygon(pts, countof(pts));
444 }
445
446 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
447         QColor fill, QColor outline, int h, double x, int y) const
448 {
449         const QString text = a.annotations().empty() ?
450                 QString() : a.annotations().back();
451         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
452                 0.0) + h;
453         const QRectF rect(x - w / 2, y - h / 2, w, h);
454
455         p.setPen(outline);
456         p.setBrush(fill);
457         p.drawRoundedRect(rect, h / 2, h / 2);
458
459         p.setPen(Qt::black);
460         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
461 }
462
463 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
464         QColor fill, QColor outline, int h, double start,
465         double end, int y) const
466 {
467         const double top = y + .5 - h / 2;
468         const double bottom = y + .5 + h / 2;
469         const vector<QString> annotations = a.annotations();
470
471         p.setPen(outline);
472         p.setBrush(fill);
473
474         // If the two ends are within 1 pixel, draw a vertical line
475         if (start + 1.0 > end) {
476                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
477                 return;
478         }
479
480         const double cap_width = min((end - start) / 4, EndCapWidth);
481
482         QPointF pts[] = {
483                 QPointF(start, y + .5f),
484                 QPointF(start + cap_width, top),
485                 QPointF(end - cap_width, top),
486                 QPointF(end, y + .5f),
487                 QPointF(end - cap_width, bottom),
488                 QPointF(start + cap_width, bottom)
489         };
490
491         p.drawConvexPolygon(pts, countof(pts));
492
493         if (annotations.empty())
494                 return;
495
496         QRectF rect(start + cap_width, y - h / 2,
497                 end - start - cap_width * 2, h);
498         if (rect.width() <= 4)
499                 return;
500
501         p.setPen(Qt::black);
502
503         // Try to find an annotation that will fit
504         QString best_annotation;
505         int best_width = 0;
506
507         for (const QString &a : annotations) {
508                 const int w = p.boundingRect(QRectF(), 0, a).width();
509                 if (w <= rect.width() && w > best_width)
510                         best_annotation = a, best_width = w;
511         }
512
513         if (best_annotation.isEmpty())
514                 best_annotation = annotations.back();
515
516         // If not ellide the last in the list
517         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
518                 best_annotation, Qt::ElideRight, rect.width()));
519 }
520
521 void DecodeTrace::draw_error(QPainter &p, const QString &message,
522         const ViewItemPaintParams &pp)
523 {
524         const int y = get_visual_y();
525
526         p.setPen(ErrorBgColour.darker());
527         p.setBrush(ErrorBgColour);
528
529         const QRectF bounding_rect =
530                 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
531         const QRectF text_rect = p.boundingRect(bounding_rect,
532                 Qt::AlignCenter, message);
533         const float r = text_rect.height() / 4;
534
535         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
536                 Qt::AbsoluteSize);
537
538         p.setPen(Qt::black);
539         p.drawText(text_rect, message);
540 }
541
542 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
543         int right) const
544 {
545         using namespace pv::data;
546         using pv::data::decode::Decoder;
547
548         double samples_per_pixel, pixels_offset;
549
550         assert(decoder_stack_); 
551
552         shared_ptr<Logic> data;
553         shared_ptr<LogicSignal> logic_signal;
554
555         const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
556
557         // We get the logic data of the first channel in the list.
558         // This works because we are currently assuming all
559         // LogicSignals have the same data/segment
560         for (const shared_ptr<Decoder> &dec : stack)
561                 if (dec && !dec->channels().empty() &&
562                         ((logic_signal = (*dec->channels().begin()).second)) &&
563                         ((data = logic_signal->logic_data())))
564                         break;
565
566         if (!data || data->logic_segments().empty())
567                 return;
568
569         const shared_ptr<LogicSegment> segment =
570                 data->logic_segments().front();
571         assert(segment);
572         const int64_t sample_count = (int64_t)segment->get_sample_count();
573         if (sample_count == 0)
574                 return;
575
576         const int64_t samples_decoded = decoder_stack_->samples_decoded();
577         if (sample_count == samples_decoded)
578                 return;
579
580         const int y = get_visual_y();
581
582         tie(pixels_offset, samples_per_pixel) =
583                 get_pixels_offset_samples_per_pixel();
584
585         const double start = max(samples_decoded /
586                 samples_per_pixel - pixels_offset, left - 1.0);
587         const double end = min(sample_count / samples_per_pixel -
588                 pixels_offset, right + 1.0);
589         const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
590
591         p.setPen(QPen(Qt::NoPen));
592         p.setBrush(Qt::white);
593         p.drawRect(no_decode_rect);
594
595         p.setPen(NoDecodeColour);
596         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
597         p.drawRect(no_decode_rect);
598 }
599
600 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
601 {
602         assert(owner_);
603         assert(decoder_stack_);
604
605         const View *view = owner_->view();
606         assert(view);
607
608         const double scale = view->scale();
609         assert(scale > 0);
610
611         const double pixels_offset =
612                 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
613
614         double samplerate = decoder_stack_->samplerate();
615
616         // Show sample rate as 1Hz when it is unknown
617         if (samplerate == 0.0)
618                 samplerate = 1.0;
619
620         return make_pair(pixels_offset, samplerate * scale);
621 }
622
623 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
624         int x_start, int x_end) const
625 {
626         double samples_per_pixel, pixels_offset;
627         tie(pixels_offset, samples_per_pixel) =
628                 get_pixels_offset_samples_per_pixel();
629
630         const uint64_t start = (uint64_t)max(
631                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
632         const uint64_t end = (uint64_t)max(
633                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
634
635         return make_pair(start, end);
636 }
637
638 int DecodeTrace::get_row_at_point(const QPoint &point)
639 {
640         if (!row_height_)
641                 return -1;
642
643         const int y = (point.y() - get_visual_y() + row_height_ / 2);
644
645         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
646         if (y < 0)
647                 return -1;
648
649         const int row = y / row_height_;
650
651         if (row >= (int)visible_rows_.size())
652                 return -1;
653
654         return row;
655 }
656
657 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
658 {
659         using namespace pv::data::decode;
660
661         if (!enabled())
662                 return QString();
663
664         const pair<uint64_t, uint64_t> sample_range =
665                 get_sample_range(point.x(), point.x() + 1);
666         const int row = get_row_at_point(point);
667         if (row < 0)
668                 return QString();
669
670         vector<pv::data::decode::Annotation> annotations;
671
672         assert(decoder_stack_);
673         decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
674                 sample_range.first, sample_range.second);
675
676         return (annotations.empty()) ?
677                 QString() : annotations[0].annotations().front();
678 }
679
680 void DecodeTrace::hover_point_changed()
681 {
682         assert(owner_);
683
684         const View *const view = owner_->view();
685         assert(view);
686
687         QPoint hp = view->hover_point();
688         QString ann = get_annotation_at_point(hp);
689
690         assert(view);
691
692         if (!row_height_ || ann.isEmpty()) {
693                 QToolTip::hideText();
694                 return;
695         }
696
697         const int hover_row = get_row_at_point(hp);
698
699         QFontMetrics m(QToolTip::font());
700         const QRect text_size = m.boundingRect(QRect(), 0, ann);
701
702         // This is OS-specific and unfortunately we can't query it, so
703         // use an approximation to at least try to minimize the error.
704         const int padding = 8;
705
706         // Make sure the tool tip doesn't overlap with the mouse cursor.
707         // If it did, the tool tip would constantly hide and re-appear.
708         // We also push it up by one row so that it appears above the
709         // decode trace, not below.
710         hp.setX(hp.x() - (text_size.width() / 2) - padding);
711
712         hp.setY(get_visual_y() - (row_height_ / 2) +
713                 (hover_row * row_height_) -
714                 row_height_ - text_size.height() - padding);
715
716         QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
717 }
718
719 void DecodeTrace::create_decoder_form(int index,
720         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
721         QFormLayout *form)
722 {
723         const GSList *l;
724
725         assert(dec);
726         const srd_decoder *const decoder = dec->decoder();
727         assert(decoder);
728
729         const bool decoder_deletable = index > 0;
730
731         pv::widgets::DecoderGroupBox *const group =
732                 new pv::widgets::DecoderGroupBox(
733                         QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
734         group->set_decoder_visible(dec->shown());
735
736         if (decoder_deletable) {
737                 delete_mapper_.setMapping(group, index);
738                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
739         }
740
741         show_hide_mapper_.setMapping(group, index);
742         connect(group, SIGNAL(show_hide_decoder()),
743                 &show_hide_mapper_, SLOT(map()));
744
745         QFormLayout *const decoder_form = new QFormLayout;
746         group->add_layout(decoder_form);
747
748         // Add the mandatory channels
749         for (l = decoder->channels; l; l = l->next) {
750                 const struct srd_channel *const pdch =
751                         (struct srd_channel *)l->data;
752                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
753                 connect(combo, SIGNAL(currentIndexChanged(int)),
754                         this, SLOT(on_channel_selected(int)));
755                 decoder_form->addRow(tr("<b>%1</b> (%2) *")
756                         .arg(QString::fromUtf8(pdch->name))
757                         .arg(QString::fromUtf8(pdch->desc)), combo);
758
759                 const ChannelSelector s = {combo, dec, pdch};
760                 channel_selectors_.push_back(s);
761         }
762
763         // Add the optional channels
764         for (l = decoder->opt_channels; l; l = l->next) {
765                 const struct srd_channel *const pdch =
766                         (struct srd_channel *)l->data;
767                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
768                 connect(combo, SIGNAL(currentIndexChanged(int)),
769                         this, SLOT(on_channel_selected(int)));
770                 decoder_form->addRow(tr("<b>%1</b> (%2)")
771                         .arg(QString::fromUtf8(pdch->name))
772                         .arg(QString::fromUtf8(pdch->desc)), combo);
773
774                 const ChannelSelector s = {combo, dec, pdch};
775                 channel_selectors_.push_back(s);
776         }
777
778         // Add the options
779         shared_ptr<binding::Decoder> binding(
780                 new binding::Decoder(decoder_stack_, dec));
781         binding->add_properties_to_form(decoder_form, true);
782
783         bindings_.push_back(binding);
784
785         form->addRow(group);
786         decoder_forms_.push_back(group);
787 }
788
789 QComboBox* DecodeTrace::create_channel_selector(
790         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
791         const srd_channel *const pdch)
792 {
793         assert(dec);
794
795         const auto sigs(session_.signals());
796
797         vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
798         std::sort(sig_list.begin(), sig_list.end(),
799                 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
800                         return a->name().compare(b->name()) < 0; });
801
802         assert(decoder_stack_);
803         const auto channel_iter = dec->channels().find(pdch);
804
805         QComboBox *selector = new QComboBox(parent);
806
807         selector->addItem("-", qVariantFromValue((void*)nullptr));
808
809         if (channel_iter == dec->channels().end())
810                 selector->setCurrentIndex(0);
811
812         for (const shared_ptr<view::Signal> &s : sig_list) {
813                 assert(s);
814                 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled()) {
815                         selector->addItem(s->name(),
816                                 qVariantFromValue((void*)s.get()));
817
818                         if (channel_iter != dec->channels().end() &&
819                                 (*channel_iter).second == s)
820                                 selector->setCurrentIndex(
821                                         selector->count() - 1);
822                 }
823         }
824
825         return selector;
826 }
827
828 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
829 {
830         assert(dec);
831
832         map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
833
834         const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
835
836         for (const ChannelSelector &s : channel_selectors_) {
837                 if (s.decoder_ != dec)
838                         break;
839
840                 const LogicSignal *const selection =
841                         (LogicSignal*)s.combo_->itemData(
842                                 s.combo_->currentIndex()).value<void*>();
843
844                 for (shared_ptr<Signal> sig : sigs)
845                         if (sig.get() == selection) {
846                                 channel_map[s.pdch_] =
847                                         dynamic_pointer_cast<LogicSignal>(sig);
848                                 break;
849                         }
850         }
851
852         dec->set_channels(channel_map);
853 }
854
855 void DecodeTrace::commit_channels()
856 {
857         assert(decoder_stack_);
858         for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
859                 commit_decoder_channels(dec);
860
861         decoder_stack_->begin_decode();
862 }
863
864 void DecodeTrace::on_new_decode_data()
865 {
866         if (owner_)
867                 owner_->row_item_appearance_changed(false, true);
868 }
869
870 void DecodeTrace::delete_pressed()
871 {
872         on_delete();
873 }
874
875 void DecodeTrace::on_delete()
876 {
877         session_.remove_decode_signal(this);
878 }
879
880 void DecodeTrace::on_channel_selected(int)
881 {
882         commit_channels();
883 }
884
885 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
886 {
887         assert(decoder);
888         assert(decoder_stack_);
889         decoder_stack_->push(shared_ptr<data::decode::Decoder>(
890                 new data::decode::Decoder(decoder)));
891         decoder_stack_->begin_decode();
892
893         create_popup_form();
894 }
895
896 void DecodeTrace::on_delete_decoder(int index)
897 {
898         decoder_stack_->remove(index);
899
900         // Update the popup
901         create_popup_form();    
902
903         decoder_stack_->begin_decode();
904 }
905
906 void DecodeTrace::on_show_hide_decoder(int index)
907 {
908         using pv::data::decode::Decoder;
909
910         const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
911
912         // Find the decoder in the stack
913         auto iter = stack.cbegin();
914         for (int i = 0; i < index; i++, iter++)
915                 assert(iter != stack.end());
916
917         shared_ptr<Decoder> dec = *iter;
918         assert(dec);
919
920         const bool show = !dec->shown();
921         dec->show(show);
922
923         assert(index < (int)decoder_forms_.size());
924         decoder_forms_[index]->set_decoder_visible(show);
925
926         if (owner_)
927                 owner_->row_item_appearance_changed(false, true);
928 }
929
930 } // namespace view
931 } // namespace pv