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