]> sigrok.org Git - pulseview.git/blob - pv/view/decodetrace.cpp
DecodeTrace: Improve annotation block drawing
[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         delete_mapper_(this),
137         show_hide_mapper_(this)
138 {
139         assert(decoder_stack_);
140
141         set_colour(DecodeColours[index % countof(DecodeColours)]);
142
143         connect(decoder_stack_.get(), SIGNAL(new_decode_data()),
144                 this, SLOT(on_new_decode_data()));
145         connect(&delete_mapper_, SIGNAL(mapped(int)),
146                 this, SLOT(on_delete_decoder(int)));
147         connect(&show_hide_mapper_, SIGNAL(mapped(int)),
148                 this, SLOT(on_show_hide_decoder(int)));
149 }
150
151 bool DecodeTrace::enabled() const
152 {
153         return true;
154 }
155
156 const std::shared_ptr<pv::data::DecoderStack>& DecodeTrace::decoder() const
157 {
158         return decoder_stack_;
159 }
160
161 pair<int, int> DecodeTrace::v_extents() const
162 {
163         const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
164         const int rows = visible_rows_.size();
165
166         return make_pair(-row_height, row_height * 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
227 void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
228 {
229         using namespace pv::data::decode;
230
231         assert(row_height_);
232
233         for (size_t i = 0; i < visible_rows_.size(); i++) {
234                 const int y = i * row_height_ + get_visual_y();
235
236                 p.setPen(QPen(Qt::NoPen));
237                 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
238
239                 if (i != 0) {
240                         const QPointF points[] = {
241                                 QPointF(pp.left(), y - ArrowSize),
242                                 QPointF(pp.left() + ArrowSize, y),
243                                 QPointF(pp.left(), y + ArrowSize)
244                         };
245                         p.drawPolygon(points, countof(points));
246                 }
247
248                 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
249                         pp.right() - pp.left(), row_height_);
250                 const QString h(visible_rows_[i].title());
251                 const int f = Qt::AlignLeft | Qt::AlignVCenter |
252                         Qt::TextDontClip;
253
254                 // Draw the outline
255                 p.setPen(QApplication::palette().color(QPalette::Base));
256                 for (int dx = -1; dx <= 1; dx++)
257                         for (int dy = -1; dy <= 1; dy++)
258                                 if (dx != 0 && dy != 0)
259                                         p.drawText(r.translated(dx, dy), f, h);
260
261                 // Draw the text
262                 p.setPen(QApplication::palette().color(QPalette::WindowText));
263                 p.drawText(r, f, h);
264         }
265 }
266
267 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
268 {
269         using pv::data::decode::Decoder;
270
271         assert(form);
272         assert(parent);
273         assert(decoder_stack_);
274
275         // Add the standard options
276         Trace::populate_popup_form(parent, form);
277
278         // Add the decoder options
279         bindings_.clear();
280         channel_selectors_.clear();
281         decoder_forms_.clear();
282
283         const list< shared_ptr<Decoder> >& stack = decoder_stack_->stack();
284
285         if (stack.empty()) {
286                 QLabel *const l = new QLabel(
287                         tr("<p><i>No decoders in the stack</i></p>"));
288                 l->setAlignment(Qt::AlignCenter);
289                 form->addRow(l);
290         } else {
291                 auto iter = stack.cbegin();
292                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
293                         shared_ptr<Decoder> dec(*iter);
294                         create_decoder_form(i, dec, parent, form);
295                 }
296
297                 form->addRow(new QLabel(
298                         tr("<i>* Required channels</i>"), parent));
299         }
300
301         // Add stacking button
302         pv::widgets::DecoderMenu *const decoder_menu =
303                 new pv::widgets::DecoderMenu(parent);
304         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
305                 this, SLOT(on_stack_decoder(srd_decoder*)));
306
307         QPushButton *const stack_button =
308                 new QPushButton(tr("Stack Decoder"), parent);
309         stack_button->setMenu(decoder_menu);
310
311         QHBoxLayout *stack_button_box = new QHBoxLayout;
312         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
313         form->addRow(stack_button_box);
314 }
315
316 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
317 {
318         QMenu *const menu = Trace::create_context_menu(parent);
319
320         menu->addSeparator();
321
322         QAction *const del = new QAction(tr("Delete"), this);
323         del->setShortcuts(QKeySequence::Delete);
324         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
325         menu->addAction(del);
326
327         return menu;
328 }
329
330 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
331                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
332                 size_t base_colour)
333 {
334         using namespace pv::data::decode;
335
336         vector<Annotation> a_block;
337         int prev_ann_pos = INT_MIN;
338
339         double samples_per_pixel, pixels_offset;
340         tie(pixels_offset, samples_per_pixel) =
341                 get_pixels_offset_samples_per_pixel();
342
343         // Gather all annotations that form a visual "block" and draw them as such
344         for (const Annotation &a : annotations) {
345
346                 const int end = a.end_sample() / samples_per_pixel - pixels_offset;
347                 const int delta = end - prev_ann_pos;
348
349                 // Some annotations are in reverse order, so we cannot
350                 // simply check for delta > 1
351                 if (abs(delta) > 1) {
352                         // Block was broken, draw it
353                         if (a_block.size() == 1)
354                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour);
355                         else
356                                 if (a_block.size() > 0)
357                                         draw_annotation_block(a_block, p, h, y, base_colour);
358
359                         a_block.clear();
360                 }
361
362                 a_block.push_back(a);
363                 prev_ann_pos = end;
364         }
365
366         if (a_block.size() == 1)
367                 draw_annotation(a_block.front(), p, h, pp, y, base_colour);
368         else
369                 draw_annotation_block(a_block, p, h, y, base_colour);
370 }
371
372 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
373         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
374         size_t base_colour) const
375 {
376         double samples_per_pixel, pixels_offset;
377         tie(pixels_offset, samples_per_pixel) =
378                 get_pixels_offset_samples_per_pixel();
379
380         const double start = a.start_sample() / samples_per_pixel -
381                 pixels_offset;
382         const double end = a.end_sample() / samples_per_pixel -
383                 pixels_offset;
384
385         const size_t colour = (base_colour + a.format()) % countof(Colours);
386         const QColor &fill = Colours[colour];
387         const QColor &outline = OutlineColours[colour];
388
389         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
390                 return;
391
392         if (a.start_sample() == a.end_sample())
393                 draw_instant(a, p, fill, outline, h, start, y);
394         else
395                 draw_range(a, p, fill, outline, h, start, end, y);
396 }
397
398 void DecodeTrace::draw_annotation_block(
399         vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
400         int y, size_t base_colour) const
401 {
402         using namespace pv::data::decode;
403
404         double samples_per_pixel, pixels_offset;
405         tie(pixels_offset, samples_per_pixel) =
406                 get_pixels_offset_samples_per_pixel();
407
408         const double start = annotations.front().start_sample() /
409                 samples_per_pixel - pixels_offset;
410         const double end = annotations.back().end_sample() /
411                 samples_per_pixel - pixels_offset;
412
413         const double top = y + .5 - h / 2;
414         const double bottom = y + .5 + h / 2;
415         const double cap_width = min((end - start) / 4, EndCapWidth);
416
417         QPointF pts[] = {
418                 QPointF(start, y + .5f),
419                 QPointF(start + cap_width, top),
420                 QPointF(end - cap_width, top),
421                 QPointF(end, y + .5f),
422                 QPointF(end - cap_width, bottom),
423                 QPointF(start + cap_width, bottom)
424         };
425
426         const size_t colour = (base_colour + annotations.front().format()) %
427                 countof(Colours);
428
429         // Check if all annotations are of the same type (i.e. we can use one color)
430         // or if we should use a neutral color (i.e. gray)
431         bool single_format = true;
432         int format = annotations.front().format();
433
434         for (const Annotation &a : annotations)
435                 if (a.format() != format) {
436                         single_format = false;
437                         break;
438                 }
439
440         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
441         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
442                 Qt::Dense4Pattern));
443
444         p.drawConvexPolygon(pts, countof(pts));
445 }
446
447 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
448         QColor fill, QColor outline, int h, double x, int y) const
449 {
450         const QString text = a.annotations().empty() ?
451                 QString() : a.annotations().back();
452         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
453                 0.0) + h;
454         const QRectF rect(x - w / 2, y - h / 2, w, h);
455
456         p.setPen(outline);
457         p.setBrush(fill);
458         p.drawRoundedRect(rect, h / 2, h / 2);
459
460         p.setPen(Qt::black);
461         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
462 }
463
464 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
465         QColor fill, QColor outline, int h, double start,
466         double end, int y) const
467 {
468         const double top = y + .5 - h / 2;
469         const double bottom = y + .5 + h / 2;
470         const vector<QString> annotations = a.annotations();
471
472         p.setPen(outline);
473         p.setBrush(fill);
474
475         // If the two ends are within 1 pixel, draw a vertical line
476         if (start + 1.0 > end) {
477                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
478                 return;
479         }
480
481         const double cap_width = min((end - start) / 4, EndCapWidth);
482
483         QPointF pts[] = {
484                 QPointF(start, y + .5f),
485                 QPointF(start + cap_width, top),
486                 QPointF(end - cap_width, top),
487                 QPointF(end, y + .5f),
488                 QPointF(end - cap_width, bottom),
489                 QPointF(start + cap_width, bottom)
490         };
491
492         p.drawConvexPolygon(pts, countof(pts));
493
494         if (annotations.empty())
495                 return;
496
497         QRectF rect(start + cap_width, y - h / 2,
498                 end - start - cap_width * 2, h);
499         if (rect.width() <= 4)
500                 return;
501
502         p.setPen(Qt::black);
503
504         // Try to find an annotation that will fit
505         QString best_annotation;
506         int best_width = 0;
507
508         for (const QString &a : annotations) {
509                 const int w = p.boundingRect(QRectF(), 0, a).width();
510                 if (w <= rect.width() && w > best_width)
511                         best_annotation = a, best_width = w;
512         }
513
514         if (best_annotation.isEmpty())
515                 best_annotation = annotations.back();
516
517         // If not ellide the last in the list
518         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
519                 best_annotation, Qt::ElideRight, rect.width()));
520 }
521
522 void DecodeTrace::draw_error(QPainter &p, const QString &message,
523         const ViewItemPaintParams &pp)
524 {
525         const int y = get_visual_y();
526
527         p.setPen(ErrorBgColour.darker());
528         p.setBrush(ErrorBgColour);
529
530         const QRectF bounding_rect =
531                 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
532         const QRectF text_rect = p.boundingRect(bounding_rect,
533                 Qt::AlignCenter, message);
534         const float r = text_rect.height() / 4;
535
536         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
537                 Qt::AbsoluteSize);
538
539         p.setPen(Qt::black);
540         p.drawText(text_rect, message);
541 }
542
543 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
544         int right) const
545 {
546         using namespace pv::data;
547         using pv::data::decode::Decoder;
548
549         double samples_per_pixel, pixels_offset;
550
551         assert(decoder_stack_); 
552
553         shared_ptr<Logic> data;
554         shared_ptr<LogicSignal> logic_signal;
555
556         const list< shared_ptr<Decoder> > &stack = decoder_stack_->stack();
557
558         // We get the logic data of the first channel in the list.
559         // This works because we are currently assuming all
560         // LogicSignals have the same data/segment
561         for (const shared_ptr<Decoder> &dec : stack)
562                 if (dec && !dec->channels().empty() &&
563                         ((logic_signal = (*dec->channels().begin()).second)) &&
564                         ((data = logic_signal->logic_data())))
565                         break;
566
567         if (!data || data->logic_segments().empty())
568                 return;
569
570         const shared_ptr<LogicSegment> segment =
571                 data->logic_segments().front();
572         assert(segment);
573         const int64_t sample_count = (int64_t)segment->get_sample_count();
574         if (sample_count == 0)
575                 return;
576
577         const int64_t samples_decoded = decoder_stack_->samples_decoded();
578         if (sample_count == samples_decoded)
579                 return;
580
581         const int y = get_visual_y();
582
583         tie(pixels_offset, samples_per_pixel) =
584                 get_pixels_offset_samples_per_pixel();
585
586         const double start = max(samples_decoded /
587                 samples_per_pixel - pixels_offset, left - 1.0);
588         const double end = min(sample_count / samples_per_pixel -
589                 pixels_offset, right + 1.0);
590         const QRectF no_decode_rect(start, y - h/2 + 0.5, end - start, h);
591
592         p.setPen(QPen(Qt::NoPen));
593         p.setBrush(Qt::white);
594         p.drawRect(no_decode_rect);
595
596         p.setPen(NoDecodeColour);
597         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
598         p.drawRect(no_decode_rect);
599 }
600
601 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
602 {
603         assert(owner_);
604         assert(decoder_stack_);
605
606         const View *view = owner_->view();
607         assert(view);
608
609         const double scale = view->scale();
610         assert(scale > 0);
611
612         const double pixels_offset =
613                 ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
614
615         double samplerate = decoder_stack_->samplerate();
616
617         // Show sample rate as 1Hz when it is unknown
618         if (samplerate == 0.0)
619                 samplerate = 1.0;
620
621         return make_pair(pixels_offset, samplerate * scale);
622 }
623
624 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
625         int x_start, int x_end) const
626 {
627         double samples_per_pixel, pixels_offset;
628         tie(pixels_offset, samples_per_pixel) =
629                 get_pixels_offset_samples_per_pixel();
630
631         const uint64_t start = (uint64_t)max(
632                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
633         const uint64_t end = (uint64_t)max(
634                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
635
636         return make_pair(start, end);
637 }
638
639 int DecodeTrace::get_row_at_point(const QPoint &point)
640 {
641         if (!row_height_)
642                 return -1;
643
644         const int y = (point.y() - get_visual_y() + row_height_ / 2);
645
646         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
647         if (y < 0)
648                 return -1;
649
650         const int row = y / row_height_;
651
652         if (row >= (int)visible_rows_.size())
653                 return -1;
654
655         return row;
656 }
657
658 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
659 {
660         using namespace pv::data::decode;
661
662         if (!enabled())
663                 return QString();
664
665         const pair<uint64_t, uint64_t> sample_range =
666                 get_sample_range(point.x(), point.x() + 1);
667         const int row = get_row_at_point(point);
668         if (row < 0)
669                 return QString();
670
671         vector<pv::data::decode::Annotation> annotations;
672
673         assert(decoder_stack_);
674         decoder_stack_->get_annotation_subset(annotations, visible_rows_[row],
675                 sample_range.first, sample_range.second);
676
677         return (annotations.empty()) ?
678                 QString() : annotations[0].annotations().front();
679 }
680
681 void DecodeTrace::hover_point_changed()
682 {
683         assert(owner_);
684
685         const View *const view = owner_->view();
686         assert(view);
687
688         QPoint hp = view->hover_point();
689         QString ann = get_annotation_at_point(hp);
690
691         assert(view);
692
693         if (!row_height_ || ann.isEmpty()) {
694                 QToolTip::hideText();
695                 return;
696         }
697
698         const int hover_row = get_row_at_point(hp);
699
700         QFontMetrics m(QToolTip::font());
701         const QRect text_size = m.boundingRect(QRect(), 0, ann);
702
703         // This is OS-specific and unfortunately we can't query it, so
704         // use an approximation to at least try to minimize the error.
705         const int padding = 8;
706
707         // Make sure the tool tip doesn't overlap with the mouse cursor.
708         // If it did, the tool tip would constantly hide and re-appear.
709         // We also push it up by one row so that it appears above the
710         // decode trace, not below.
711         hp.setX(hp.x() - (text_size.width() / 2) - padding);
712
713         hp.setY(get_visual_y() - (row_height_ / 2) +
714                 (hover_row * row_height_) -
715                 row_height_ - text_size.height() - padding);
716
717         QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
718 }
719
720 void DecodeTrace::create_decoder_form(int index,
721         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
722         QFormLayout *form)
723 {
724         const GSList *l;
725
726         assert(dec);
727         const srd_decoder *const decoder = dec->decoder();
728         assert(decoder);
729
730         const bool decoder_deletable = index > 0;
731
732         pv::widgets::DecoderGroupBox *const group =
733                 new pv::widgets::DecoderGroupBox(
734                         QString::fromUtf8(decoder->name), nullptr, decoder_deletable);
735         group->set_decoder_visible(dec->shown());
736
737         if (decoder_deletable) {
738                 delete_mapper_.setMapping(group, index);
739                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
740         }
741
742         show_hide_mapper_.setMapping(group, index);
743         connect(group, SIGNAL(show_hide_decoder()),
744                 &show_hide_mapper_, SLOT(map()));
745
746         QFormLayout *const decoder_form = new QFormLayout;
747         group->add_layout(decoder_form);
748
749         // Add the mandatory channels
750         for (l = decoder->channels; l; l = l->next) {
751                 const struct srd_channel *const pdch =
752                         (struct srd_channel *)l->data;
753                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
754                 connect(combo, SIGNAL(currentIndexChanged(int)),
755                         this, SLOT(on_channel_selected(int)));
756                 decoder_form->addRow(tr("<b>%1</b> (%2) *")
757                         .arg(QString::fromUtf8(pdch->name))
758                         .arg(QString::fromUtf8(pdch->desc)), combo);
759
760                 const ChannelSelector s = {combo, dec, pdch};
761                 channel_selectors_.push_back(s);
762         }
763
764         // Add the optional channels
765         for (l = decoder->opt_channels; l; l = l->next) {
766                 const struct srd_channel *const pdch =
767                         (struct srd_channel *)l->data;
768                 QComboBox *const combo = create_channel_selector(parent, dec, pdch);
769                 connect(combo, SIGNAL(currentIndexChanged(int)),
770                         this, SLOT(on_channel_selected(int)));
771                 decoder_form->addRow(tr("<b>%1</b> (%2)")
772                         .arg(QString::fromUtf8(pdch->name))
773                         .arg(QString::fromUtf8(pdch->desc)), combo);
774
775                 const ChannelSelector s = {combo, dec, pdch};
776                 channel_selectors_.push_back(s);
777         }
778
779         // Add the options
780         shared_ptr<binding::Decoder> binding(
781                 new binding::Decoder(decoder_stack_, dec));
782         binding->add_properties_to_form(decoder_form, true);
783
784         bindings_.push_back(binding);
785
786         form->addRow(group);
787         decoder_forms_.push_back(group);
788 }
789
790 QComboBox* DecodeTrace::create_channel_selector(
791         QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
792         const srd_channel *const pdch)
793 {
794         assert(dec);
795
796         const auto sigs(session_.signals());
797
798         vector< shared_ptr<Signal> > sig_list(sigs.begin(), sigs.end());
799         std::sort(sig_list.begin(), sig_list.end(),
800                 [](const shared_ptr<Signal> &a, const shared_ptr<Signal> b) {
801                         return a->name().compare(b->name()) < 0; });
802
803         assert(decoder_stack_);
804         const auto channel_iter = dec->channels().find(pdch);
805
806         QComboBox *selector = new QComboBox(parent);
807
808         selector->addItem("-", qVariantFromValue((void*)nullptr));
809
810         if (channel_iter == dec->channels().end())
811                 selector->setCurrentIndex(0);
812
813         for (const shared_ptr<view::Signal> &s : sig_list) {
814                 assert(s);
815                 if (dynamic_pointer_cast<LogicSignal>(s) && s->enabled()) {
816                         selector->addItem(s->name(),
817                                 qVariantFromValue((void*)s.get()));
818
819                         if (channel_iter != dec->channels().end() &&
820                                 (*channel_iter).second == s)
821                                 selector->setCurrentIndex(
822                                         selector->count() - 1);
823                 }
824         }
825
826         return selector;
827 }
828
829 void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
830 {
831         assert(dec);
832
833         map<const srd_channel*, shared_ptr<LogicSignal> > channel_map;
834
835         const unordered_set< shared_ptr<Signal> > sigs(session_.signals());
836
837         for (const ChannelSelector &s : channel_selectors_) {
838                 if (s.decoder_ != dec)
839                         break;
840
841                 const LogicSignal *const selection =
842                         (LogicSignal*)s.combo_->itemData(
843                                 s.combo_->currentIndex()).value<void*>();
844
845                 for (shared_ptr<Signal> sig : sigs)
846                         if (sig.get() == selection) {
847                                 channel_map[s.pdch_] =
848                                         dynamic_pointer_cast<LogicSignal>(sig);
849                                 break;
850                         }
851         }
852
853         dec->set_channels(channel_map);
854 }
855
856 void DecodeTrace::commit_channels()
857 {
858         assert(decoder_stack_);
859         for (shared_ptr<data::decode::Decoder> dec : decoder_stack_->stack())
860                 commit_decoder_channels(dec);
861
862         decoder_stack_->begin_decode();
863 }
864
865 void DecodeTrace::on_new_decode_data()
866 {
867         if (owner_)
868                 owner_->row_item_appearance_changed(false, true);
869 }
870
871 void DecodeTrace::delete_pressed()
872 {
873         on_delete();
874 }
875
876 void DecodeTrace::on_delete()
877 {
878         session_.remove_decode_signal(this);
879 }
880
881 void DecodeTrace::on_channel_selected(int)
882 {
883         commit_channels();
884 }
885
886 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
887 {
888         assert(decoder);
889         assert(decoder_stack_);
890         decoder_stack_->push(shared_ptr<data::decode::Decoder>(
891                 new data::decode::Decoder(decoder)));
892         decoder_stack_->begin_decode();
893
894         create_popup_form();
895 }
896
897 void DecodeTrace::on_delete_decoder(int index)
898 {
899         decoder_stack_->remove(index);
900
901         // Update the popup
902         create_popup_form();    
903
904         decoder_stack_->begin_decode();
905 }
906
907 void DecodeTrace::on_show_hide_decoder(int index)
908 {
909         using pv::data::decode::Decoder;
910
911         const list< shared_ptr<Decoder> > stack(decoder_stack_->stack());
912
913         // Find the decoder in the stack
914         auto iter = stack.cbegin();
915         for (int i = 0; i < index; i++, iter++)
916                 assert(iter != stack.end());
917
918         shared_ptr<Decoder> dec = *iter;
919         assert(dec);
920
921         const bool show = !dec->shown();
922         dec->show(show);
923
924         assert(index < (int)decoder_forms_.size());
925         decoder_forms_[index]->set_decoder_visible(show);
926
927         if (owner_)
928                 owner_->row_item_appearance_changed(false, true);
929 }
930
931 } // namespace view
932 } // namespace pv