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