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