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