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