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