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