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