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