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