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