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