]> sigrok.org Git - pulseview.git/blame - pv/view/decodetrace.cpp
Add a tooltip for the decoder name in the decoder popup.
[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
aca9aa83 43#include <pv/data/decode/annotation.hpp>
2acdb232 44#include <pv/data/decode/decoder.hpp>
aca9aa83 45#include <pv/data/decoderstack.hpp>
2acdb232 46#include <pv/data/logic.hpp>
f3d66e52 47#include <pv/data/logicsegment.hpp>
aca9aa83
UH
48#include <pv/session.hpp>
49#include <pv/strnatcmp.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 55using std::all_of;
819f4c25 56using std::list;
7f8517f6 57using std::make_pair;
819f4c25 58using std::max;
a5d93c27 59using std::make_pair;
819f4c25
JH
60using std::map;
61using std::min;
6f925ba9 62using std::out_of_range;
7f8517f6 63using std::pair;
f9abf97e 64using std::shared_ptr;
067bb624 65using std::make_shared;
53e35b2d 66using std::tie;
78b0af3e 67using std::unordered_set;
819f4c25 68using std::vector;
55d3603d
JH
69
70namespace pv {
f4e57597
SA
71namespace views {
72namespace TraceView {
55d3603d 73
b9329558 74const QColor DecodeTrace::DecodeColours[4] = {
06bb4e6a
JH
75 QColor(0xEF, 0x29, 0x29), // Red
76 QColor(0xFC, 0xE9, 0x4F), // Yellow
77 QColor(0x8A, 0xE2, 0x34), // Green
78 QColor(0x72, 0x9F, 0xCF) // Blue
79};
80
b9329558 81const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
5dfeb70f 82const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
ad50ac1a 83
88908838 84const int DecodeTrace::ArrowSize = 4;
06e810f2 85const double DecodeTrace::EndCapWidth = 5;
aee9dcf3 86const int DecodeTrace::RowTitleMargin = 10;
06e810f2
JH
87const int DecodeTrace::DrawPadding = 100;
88
287d607f
JH
89const QColor DecodeTrace::Colours[16] = {
90 QColor(0xEF, 0x29, 0x29),
91 QColor(0xF6, 0x6A, 0x32),
92 QColor(0xFC, 0xAE, 0x3E),
93 QColor(0xFB, 0xCA, 0x47),
94 QColor(0xFC, 0xE9, 0x4F),
95 QColor(0xCD, 0xF0, 0x40),
96 QColor(0x8A, 0xE2, 0x34),
97 QColor(0x4E, 0xDC, 0x44),
98 QColor(0x55, 0xD7, 0x95),
99 QColor(0x64, 0xD1, 0xD2),
100 QColor(0x72, 0x9F, 0xCF),
101 QColor(0xD4, 0x76, 0xC4),
102 QColor(0x9D, 0x79, 0xB9),
103 QColor(0xAD, 0x7F, 0xA8),
104 QColor(0xC2, 0x62, 0x9B),
105 QColor(0xD7, 0x47, 0x6F)
106};
107
108const QColor DecodeTrace::OutlineColours[16] = {
109 QColor(0x77, 0x14, 0x14),
110 QColor(0x7B, 0x35, 0x19),
111 QColor(0x7E, 0x57, 0x1F),
112 QColor(0x7D, 0x65, 0x23),
113 QColor(0x7E, 0x74, 0x27),
114 QColor(0x66, 0x78, 0x20),
115 QColor(0x45, 0x71, 0x1A),
116 QColor(0x27, 0x6E, 0x22),
117 QColor(0x2A, 0x6B, 0x4A),
118 QColor(0x32, 0x68, 0x69),
119 QColor(0x39, 0x4F, 0x67),
120 QColor(0x6A, 0x3B, 0x62),
121 QColor(0x4E, 0x3C, 0x5C),
122 QColor(0x56, 0x3F, 0x54),
123 QColor(0x61, 0x31, 0x4D),
124 QColor(0x6B, 0x23, 0x37)
06e810f2
JH
125};
126
2b81ae46 127DecodeTrace::DecodeTrace(pv::Session &session,
bb7dd726 128 shared_ptr<data::SignalBase> signalbase, int index) :
bf0edd2b 129 Trace(signalbase),
8dbbc7f0 130 session_(session),
8dbbc7f0 131 row_height_(0),
eee89ff8 132 max_visible_rows_(0),
8dbbc7f0
JH
133 delete_mapper_(this),
134 show_hide_mapper_(this)
55d3603d 135{
6f925ba9 136 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
e0fc5810 137
752281db
SA
138 // Determine shortest string we want to see displayed in full
139 QFontMetrics m(QApplication::font());
140 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
141
455bc29a
SA
142 base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
143 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
9cef9567 144
bb7dd726 145 connect(decoder_stack.get(), SIGNAL(new_decode_data()),
9cef9567 146 this, SLOT(on_new_decode_data()));
8dbbc7f0 147 connect(&delete_mapper_, SIGNAL(mapped(int)),
613d097c 148 this, SLOT(on_delete_decoder(int)));
8dbbc7f0 149 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
dd048a7e 150 this, SLOT(on_show_hide_decoder(int)));
55d3603d
JH
151}
152
b9329558 153bool DecodeTrace::enabled() const
55d3603d
JH
154{
155 return true;
156}
157
6f925ba9 158shared_ptr<data::SignalBase> DecodeTrace::base() const
b6b267bb 159{
bb7dd726 160 return base_;
b6b267bb
JH
161}
162
a5d93c27
JH
163pair<int, int> DecodeTrace::v_extents() const
164{
5b5fa4da 165 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
796e1360 166
e40a79cb
SA
167 // Make an empty decode trace appear symmetrical
168 const int row_count = max(1, max_visible_rows_);
169
170 return make_pair(-row_height, row_height * row_count);
a5d93c27
JH
171}
172
5b5fa4da 173void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
fe08b6e8 174{
3eb29afd 175 Trace::paint_back(p, pp);
97904bf7 176 paint_axis(p, pp, get_visual_y());
fe08b6e8
JH
177}
178
5b5fa4da 179void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
55d3603d 180{
f9101a91 181 using namespace pv::data::decode;
9472f447 182
6f925ba9 183 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726 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
bb7dd726
SA
189 assert(decoder_stack);
190 const QString err = decoder_stack->error_message();
2ad82c2e 191 if (!err.isEmpty()) {
3eb29afd
JH
192 draw_unresolved_period(
193 p, annotation_height, pp.left(), pp.right());
194 draw_error(p, err, pp);
5dfeb70f
JH
195 return;
196 }
197
cd0c558b
SA
198 // Set default pen to allow for text width calculation
199 p.setPen(Qt::black);
200
f9101a91 201 // Iterate through the rows
be9e7b4b 202 int y = get_visual_y();
3eb29afd
JH
203 pair<uint64_t, uint64_t> sample_range = get_sample_range(
204 pp.left(), pp.right());
5dfeb70f 205
bb7dd726 206 const vector<Row> rows(decoder_stack->get_visible_rows());
7f8517f6 207
8dbbc7f0 208 visible_rows_.clear();
4fb5fb99 209 for (const Row& row : rows) {
aee9dcf3
SA
210 // Cache the row title widths
211 int row_title_width;
212 try {
213 row_title_width = row_title_widths_.at(row);
6f925ba9 214 } catch (out_of_range) {
aee9dcf3
SA
215 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
216 RowTitleMargin;
217 row_title_widths_[row] = w;
218 row_title_width = w;
219 }
220
221 // Determine the row's color
287d607f
JH
222 size_t base_colour = 0x13579BDF;
223 boost::hash_combine(base_colour, this);
224 boost::hash_combine(base_colour, row.decoder());
225 boost::hash_combine(base_colour, row.row());
226 base_colour >>= 16;
227
f9101a91 228 vector<Annotation> annotations;
bb7dd726 229 decoder_stack->get_annotation_subset(annotations, row,
7f8517f6 230 sample_range.first, sample_range.second);
f9101a91 231 if (!annotations.empty()) {
50631798 232 draw_annotations(annotations, p, annotation_height, pp, y,
aee9dcf3 233 base_colour, row_title_width);
50631798 234
8dbbc7f0 235 y += row_height_;
88908838 236
4fb5fb99 237 visible_rows_.push_back(row);
f9101a91 238 }
7e674e43 239 }
5dfeb70f 240
f9101a91 241 // Draw the hatching
3eb29afd 242 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
eee89ff8 243
a303c2d8
SA
244 if ((int)visible_rows_.size() > max_visible_rows_)
245 owner_->extents_changed(false, true);
246
eee89ff8 247 // Update the maximum row count if needed
6f925ba9 248 max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
55d3603d
JH
249}
250
5b5fa4da 251void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
88908838
JH
252{
253 using namespace pv::data::decode;
254
8dbbc7f0 255 assert(row_height_);
88908838 256
2ad82c2e 257 for (size_t i = 0; i < visible_rows_.size(); i++) {
8dbbc7f0 258 const int y = i * row_height_ + get_visual_y();
88908838
JH
259
260 p.setPen(QPen(Qt::NoPen));
261 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
262
2ad82c2e 263 if (i != 0) {
88908838 264 const QPointF points[] = {
3eb29afd
JH
265 QPointF(pp.left(), y - ArrowSize),
266 QPointF(pp.left() + ArrowSize, y),
267 QPointF(pp.left(), y + ArrowSize)
88908838
JH
268 };
269 p.drawPolygon(points, countof(points));
270 }
271
3eb29afd
JH
272 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
273 pp.right() - pp.left(), row_height_);
8dbbc7f0 274 const QString h(visible_rows_[i].title());
88908838
JH
275 const int f = Qt::AlignLeft | Qt::AlignVCenter |
276 Qt::TextDontClip;
277
278 // Draw the outline
279 p.setPen(QApplication::palette().color(QPalette::Base));
280 for (int dx = -1; dx <= 1; dx++)
281 for (int dy = -1; dy <= 1; dy++)
282 if (dx != 0 && dy != 0)
283 p.drawText(r.translated(dx, dy), f, h);
284
285 // Draw the text
286 p.setPen(QApplication::palette().color(QPalette::WindowText));
287 p.drawText(r, f, h);
288 }
289}
290
b9329558 291void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
4e5a4405 292{
613d097c
JH
293 using pv::data::decode::Decoder;
294
6f925ba9 295 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726 296
4e5a4405
JH
297 assert(form);
298 assert(parent);
bb7dd726 299 assert(decoder_stack);
4e5a4405 300
7491a29f 301 // Add the standard options
4e5a4405
JH
302 Trace::populate_popup_form(parent, form);
303
7491a29f 304 // Add the decoder options
8dbbc7f0
JH
305 bindings_.clear();
306 channel_selectors_.clear();
307 decoder_forms_.clear();
4e5a4405 308
bb7dd726 309 const list< shared_ptr<Decoder> >& stack = decoder_stack->stack();
4e5a4405 310
2ad82c2e 311 if (stack.empty()) {
5069084a
JH
312 QLabel *const l = new QLabel(
313 tr("<p><i>No decoders in the stack</i></p>"));
314 l->setAlignment(Qt::AlignCenter);
315 form->addRow(l);
2ad82c2e 316 } else {
f46e495e 317 auto iter = stack.cbegin();
5069084a
JH
318 for (int i = 0; i < (int)stack.size(); i++, iter++) {
319 shared_ptr<Decoder> dec(*iter);
320 create_decoder_form(i, dec, parent, form);
321 }
322
323 form->addRow(new QLabel(
8bd26d8b 324 tr("<i>* Required channels</i>"), parent));
5069084a 325 }
4e5a4405 326
ce94e4fd 327 // Add stacking button
ce94e4fd
JH
328 pv::widgets::DecoderMenu *const decoder_menu =
329 new pv::widgets::DecoderMenu(parent);
7491a29f
JH
330 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
331 this, SLOT(on_stack_decoder(srd_decoder*)));
332
333 QPushButton *const stack_button =
334 new QPushButton(tr("Stack Decoder"), parent);
ce94e4fd 335 stack_button->setMenu(decoder_menu);
b14a9371 336 stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
ce94e4fd
JH
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;
c063290a 441 const double end = a.end_sample() / samples_per_pixel - pixels_offset;
287d607f
JH
442
443 const size_t colour = (base_colour + a.format()) % countof(Colours);
f765c3db
SA
444 p.setPen(OutlineColours[colour]);
445 p.setBrush(Colours[colour]);
06e810f2 446
3eb29afd 447 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
06e810f2
JH
448 return;
449
450 if (a.start_sample() == a.end_sample())
f765c3db 451 draw_instant(a, p, h, start, y);
06e810f2 452 else
c063290a 453 draw_range(a, p, h, start, end, y, pp, row_title_width);
06e810f2
JH
454}
455
33707990
SA
456void DecodeTrace::draw_annotation_block(
457 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
458 int y, size_t base_colour) const
50631798 459{
33707990
SA
460 using namespace pv::data::decode;
461
bdc2a99b
SA
462 if (annotations.empty())
463 return;
464
50631798
SA
465 double samples_per_pixel, pixels_offset;
466 tie(pixels_offset, samples_per_pixel) =
467 get_pixels_offset_samples_per_pixel();
468
33707990
SA
469 const double start = annotations.front().start_sample() /
470 samples_per_pixel - pixels_offset;
471 const double end = annotations.back().end_sample() /
472 samples_per_pixel - pixels_offset;
473
474 const double top = y + .5 - h / 2;
475 const double bottom = y + .5 + h / 2;
33707990
SA
476
477 const size_t colour = (base_colour + annotations.front().format()) %
478 countof(Colours);
479
480 // Check if all annotations are of the same type (i.e. we can use one color)
481 // or if we should use a neutral color (i.e. gray)
8c0302f5 482 const int format = annotations.front().format();
6f925ba9 483 const bool single_format = all_of(
8c0302f5
JH
484 annotations.begin(), annotations.end(),
485 [&](const Annotation &a) { return a.format() == format; });
50631798 486
33707990
SA
487 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
488 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
489 Qt::Dense4Pattern));
086f4df5 490 p.drawRoundedRect(
c063290a 491 QRectF(start, top, end - start, bottom - top), h / 4, h / 4);
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 =
3eb29afd 579 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), 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;
7491a29f
JH
778
779 assert(dec);
780 const srd_decoder *const decoder = dec->decoder();
781 assert(decoder);
782
ff59fa2c
SA
783 const bool decoder_deletable = index > 0;
784
204bae45 785 pv::widgets::DecoderGroupBox *const group =
27e8df22 786 new pv::widgets::DecoderGroupBox(
580b4f25
UH
787 QString::fromUtf8(decoder->name),
788 tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
789 QString::fromUtf8(decoder->desc)),
790 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
c063290a 966 create_popup_form();
613d097c 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