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