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