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