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