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