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