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