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