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