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