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