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