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