]> sigrok.org Git - pulseview.git/blame - pv/view/decodetrace.cpp
Random simplifications, cosmetics/whitespace/consistency fixes.
[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
efdec55a 17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
55d3603d
JH
18 */
19
20extern "C" {
21#include <libsigrokdecode/libsigrokdecode.h>
22}
23
c3a740dd
JH
24#include <mutex>
25
06bb4e6a
JH
26#include <extdef.h>
27
53e35b2d
JH
28#include <tuple>
29
a855d71e 30#include <boost/functional/hash.hpp>
b213ef09 31
c51482b3 32#include <QAction>
d7c0ca4a 33#include <QApplication>
4e5a4405
JH
34#include <QComboBox>
35#include <QFormLayout>
36#include <QLabel>
b213ef09 37#include <QMenu>
ce94e4fd 38#include <QPushButton>
e2f90c50 39#include <QToolTip>
c51482b3 40
2acdb232
JH
41#include "decodetrace.hpp"
42
f65cd27b 43#include <pv/session.hpp>
51307fd6 44#include <pv/strnatcmp.hpp>
2acdb232
JH
45#include <pv/data/decoderstack.hpp>
46#include <pv/data/decode/decoder.hpp>
47#include <pv/data/logic.hpp>
f3d66e52 48#include <pv/data/logicsegment.hpp>
2acdb232 49#include <pv/data/decode/annotation.hpp>
2acdb232
JH
50#include <pv/view/view.hpp>
51#include <pv/view/viewport.hpp>
52#include <pv/widgets/decodergroupbox.hpp>
53#include <pv/widgets/decodermenu.hpp>
119aff65 54
6f925ba9 55using std::all_of;
819f4c25 56using std::list;
7f8517f6 57using std::make_pair;
819f4c25 58using std::max;
a5d93c27 59using std::make_pair;
819f4c25
JH
60using std::map;
61using std::min;
6f925ba9 62using std::out_of_range;
7f8517f6 63using std::pair;
f9abf97e 64using std::shared_ptr;
067bb624 65using std::make_shared;
53e35b2d 66using std::tie;
78b0af3e 67using std::unordered_set;
819f4c25 68using std::vector;
55d3603d
JH
69
70namespace pv {
f4e57597
SA
71namespace views {
72namespace TraceView {
55d3603d 73
b9329558 74const QColor DecodeTrace::DecodeColours[4] = {
06bb4e6a
JH
75 QColor(0xEF, 0x29, 0x29), // Red
76 QColor(0xFC, 0xE9, 0x4F), // Yellow
77 QColor(0x8A, 0xE2, 0x34), // Green
78 QColor(0x72, 0x9F, 0xCF) // Blue
79};
80
b9329558 81const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
5dfeb70f 82const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
ad50ac1a 83
88908838 84const int DecodeTrace::ArrowSize = 4;
06e810f2 85const double DecodeTrace::EndCapWidth = 5;
aee9dcf3 86const int DecodeTrace::RowTitleMargin = 10;
06e810f2
JH
87const int DecodeTrace::DrawPadding = 100;
88
287d607f
JH
89const QColor DecodeTrace::Colours[16] = {
90 QColor(0xEF, 0x29, 0x29),
91 QColor(0xF6, 0x6A, 0x32),
92 QColor(0xFC, 0xAE, 0x3E),
93 QColor(0xFB, 0xCA, 0x47),
94 QColor(0xFC, 0xE9, 0x4F),
95 QColor(0xCD, 0xF0, 0x40),
96 QColor(0x8A, 0xE2, 0x34),
97 QColor(0x4E, 0xDC, 0x44),
98 QColor(0x55, 0xD7, 0x95),
99 QColor(0x64, 0xD1, 0xD2),
100 QColor(0x72, 0x9F, 0xCF),
101 QColor(0xD4, 0x76, 0xC4),
102 QColor(0x9D, 0x79, 0xB9),
103 QColor(0xAD, 0x7F, 0xA8),
104 QColor(0xC2, 0x62, 0x9B),
105 QColor(0xD7, 0x47, 0x6F)
106};
107
108const QColor DecodeTrace::OutlineColours[16] = {
109 QColor(0x77, 0x14, 0x14),
110 QColor(0x7B, 0x35, 0x19),
111 QColor(0x7E, 0x57, 0x1F),
112 QColor(0x7D, 0x65, 0x23),
113 QColor(0x7E, 0x74, 0x27),
114 QColor(0x66, 0x78, 0x20),
115 QColor(0x45, 0x71, 0x1A),
116 QColor(0x27, 0x6E, 0x22),
117 QColor(0x2A, 0x6B, 0x4A),
118 QColor(0x32, 0x68, 0x69),
119 QColor(0x39, 0x4F, 0x67),
120 QColor(0x6A, 0x3B, 0x62),
121 QColor(0x4E, 0x3C, 0x5C),
122 QColor(0x56, 0x3F, 0x54),
123 QColor(0x61, 0x31, 0x4D),
124 QColor(0x6B, 0x23, 0x37)
06e810f2
JH
125};
126
2b81ae46 127DecodeTrace::DecodeTrace(pv::Session &session,
bb7dd726 128 shared_ptr<data::SignalBase> signalbase, int index) :
bf0edd2b 129 Trace(signalbase),
8dbbc7f0 130 session_(session),
8dbbc7f0 131 row_height_(0),
eee89ff8 132 max_visible_rows_(0),
8dbbc7f0
JH
133 delete_mapper_(this),
134 show_hide_mapper_(this)
55d3603d 135{
6f925ba9 136 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
e0fc5810 137
752281db
SA
138 // Determine shortest string we want to see displayed in full
139 QFontMetrics m(QApplication::font());
140 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
141
455bc29a
SA
142 base_->set_name(QString::fromUtf8(decoder_stack->stack().front()->decoder()->name));
143 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
9cef9567 144
bb7dd726 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
6f925ba9 158shared_ptr<data::SignalBase> DecodeTrace::base() const
b6b267bb 159{
bb7dd726 160 return base_;
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
e40a79cb
SA
167 // Make an empty decode trace appear symmetrical
168 const int row_count = max(1, max_visible_rows_);
169
170 return make_pair(-row_height, row_height * row_count);
a5d93c27
JH
171}
172
5b5fa4da 173void DecodeTrace::paint_back(QPainter &p, const ViewItemPaintParams &pp)
fe08b6e8 174{
3eb29afd 175 Trace::paint_back(p, pp);
97904bf7 176 paint_axis(p, pp, get_visual_y());
fe08b6e8
JH
177}
178
5b5fa4da 179void DecodeTrace::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
55d3603d 180{
f9101a91 181 using namespace pv::data::decode;
9472f447 182
6f925ba9 183 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726 184
0ce3d18c
JH
185 const int text_height = ViewItemPaintParams::text_height();
186 row_height_ = (text_height * 6) / 4;
187 const int annotation_height = (text_height * 5) / 4;
5dfeb70f 188
bb7dd726
SA
189 assert(decoder_stack);
190 const QString err = decoder_stack->error_message();
2ad82c2e 191 if (!err.isEmpty()) {
3eb29afd
JH
192 draw_unresolved_period(
193 p, annotation_height, pp.left(), pp.right());
194 draw_error(p, err, pp);
5dfeb70f
JH
195 return;
196 }
197
cd0c558b
SA
198 // Set default pen to allow for text width calculation
199 p.setPen(Qt::black);
200
f9101a91 201 // Iterate through the rows
be9e7b4b 202 int y = get_visual_y();
3eb29afd
JH
203 pair<uint64_t, uint64_t> sample_range = get_sample_range(
204 pp.left(), pp.right());
5dfeb70f 205
bb7dd726 206 const vector<Row> rows(decoder_stack->get_visible_rows());
7f8517f6 207
8dbbc7f0 208 visible_rows_.clear();
4fb5fb99 209 for (const Row& row : rows) {
aee9dcf3
SA
210 // Cache the row title widths
211 int row_title_width;
212 try {
213 row_title_width = row_title_widths_.at(row);
6f925ba9 214 } catch (out_of_range) {
aee9dcf3
SA
215 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
216 RowTitleMargin;
217 row_title_widths_[row] = w;
218 row_title_width = w;
219 }
220
221 // Determine the row's color
287d607f
JH
222 size_t base_colour = 0x13579BDF;
223 boost::hash_combine(base_colour, this);
224 boost::hash_combine(base_colour, row.decoder());
225 boost::hash_combine(base_colour, row.row());
226 base_colour >>= 16;
227
f9101a91 228 vector<Annotation> annotations;
bb7dd726 229 decoder_stack->get_annotation_subset(annotations, row,
7f8517f6 230 sample_range.first, sample_range.second);
f9101a91 231 if (!annotations.empty()) {
50631798 232 draw_annotations(annotations, p, annotation_height, pp, y,
aee9dcf3 233 base_colour, row_title_width);
50631798 234
8dbbc7f0 235 y += row_height_;
88908838 236
4fb5fb99 237 visible_rows_.push_back(row);
f9101a91 238 }
7e674e43 239 }
5dfeb70f 240
f9101a91 241 // Draw the hatching
3eb29afd 242 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
eee89ff8 243
a303c2d8
SA
244 if ((int)visible_rows_.size() > max_visible_rows_)
245 owner_->extents_changed(false, true);
246
eee89ff8 247 // Update the maximum row count if needed
6f925ba9 248 max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
55d3603d
JH
249}
250
5b5fa4da 251void DecodeTrace::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
88908838
JH
252{
253 using namespace pv::data::decode;
254
8dbbc7f0 255 assert(row_height_);
88908838 256
2ad82c2e 257 for (size_t i = 0; i < visible_rows_.size(); i++) {
8dbbc7f0 258 const int y = i * row_height_ + get_visual_y();
88908838
JH
259
260 p.setPen(QPen(Qt::NoPen));
261 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
262
2ad82c2e 263 if (i != 0) {
88908838 264 const QPointF points[] = {
3eb29afd
JH
265 QPointF(pp.left(), y - ArrowSize),
266 QPointF(pp.left() + ArrowSize, y),
267 QPointF(pp.left(), y + ArrowSize)
88908838
JH
268 };
269 p.drawPolygon(points, countof(points));
270 }
271
3eb29afd
JH
272 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
273 pp.right() - pp.left(), row_height_);
8dbbc7f0 274 const QString h(visible_rows_[i].title());
88908838
JH
275 const int f = Qt::AlignLeft | Qt::AlignVCenter |
276 Qt::TextDontClip;
277
278 // Draw the outline
279 p.setPen(QApplication::palette().color(QPalette::Base));
280 for (int dx = -1; dx <= 1; dx++)
281 for (int dy = -1; dy <= 1; dy++)
282 if (dx != 0 && dy != 0)
283 p.drawText(r.translated(dx, dy), f, h);
284
285 // Draw the text
286 p.setPen(QApplication::palette().color(QPalette::WindowText));
287 p.drawText(r, f, h);
288 }
289}
290
b9329558 291void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
4e5a4405 292{
613d097c
JH
293 using pv::data::decode::Decoder;
294
6f925ba9 295 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726 296
4e5a4405
JH
297 assert(form);
298 assert(parent);
bb7dd726 299 assert(decoder_stack);
4e5a4405 300
7491a29f 301 // Add the standard options
4e5a4405
JH
302 Trace::populate_popup_form(parent, form);
303
7491a29f 304 // Add the decoder options
8dbbc7f0
JH
305 bindings_.clear();
306 channel_selectors_.clear();
307 decoder_forms_.clear();
4e5a4405 308
bb7dd726 309 const list< shared_ptr<Decoder> >& stack = decoder_stack->stack();
4e5a4405 310
2ad82c2e 311 if (stack.empty()) {
5069084a
JH
312 QLabel *const l = new QLabel(
313 tr("<p><i>No decoders in the stack</i></p>"));
314 l->setAlignment(Qt::AlignCenter);
315 form->addRow(l);
2ad82c2e 316 } else {
f46e495e 317 auto iter = stack.cbegin();
5069084a
JH
318 for (int i = 0; i < (int)stack.size(); i++, iter++) {
319 shared_ptr<Decoder> dec(*iter);
320 create_decoder_form(i, dec, parent, form);
321 }
322
323 form->addRow(new QLabel(
8bd26d8b 324 tr("<i>* Required channels</i>"), parent));
5069084a 325 }
4e5a4405 326
ce94e4fd 327 // Add stacking button
ce94e4fd
JH
328 pv::widgets::DecoderMenu *const decoder_menu =
329 new pv::widgets::DecoderMenu(parent);
7491a29f
JH
330 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
331 this, SLOT(on_stack_decoder(srd_decoder*)));
332
333 QPushButton *const stack_button =
334 new QPushButton(tr("Stack Decoder"), parent);
ce94e4fd
JH
335 stack_button->setMenu(decoder_menu);
336
337 QHBoxLayout *stack_button_box = new QHBoxLayout;
338 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
339 form->addRow(stack_button_box);
4e5a4405
JH
340}
341
b9329558 342QMenu* DecodeTrace::create_context_menu(QWidget *parent)
c51482b3
JH
343{
344 QMenu *const menu = Trace::create_context_menu(parent);
345
346 menu->addSeparator();
347
348 QAction *const del = new QAction(tr("Delete"), this);
a2d21018 349 del->setShortcuts(QKeySequence::Delete);
c51482b3
JH
350 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
351 menu->addAction(del);
352
353 return menu;
354}
355
50631798
SA
356void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
357 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
aee9dcf3 358 size_t base_colour, int row_title_width)
50631798
SA
359{
360 using namespace pv::data::decode;
361
362 vector<Annotation> a_block;
bdc2a99b 363 int p_end = INT_MIN;
50631798
SA
364
365 double samples_per_pixel, pixels_offset;
366 tie(pixels_offset, samples_per_pixel) =
367 get_pixels_offset_samples_per_pixel();
368
bdc2a99b
SA
369 // Sort the annotations by start sample so that decoders
370 // can't confuse us by creating annotations out of order
371 stable_sort(annotations.begin(), annotations.end(),
372 [](const Annotation &a, const Annotation &b) {
373 return a.start_sample() < b.start_sample(); });
374
50631798
SA
375 // Gather all annotations that form a visual "block" and draw them as such
376 for (const Annotation &a : annotations) {
377
bdc2a99b
SA
378 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
379 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
380 const int a_width = a_end - a_start;
381
382 const int delta = a_end - p_end;
383
384 bool a_is_separate = false;
385
386 // Annotation wider than the threshold for a useful label width?
752281db 387 if (a_width >= min_useful_label_width_) {
bdc2a99b
SA
388 for (const QString &ann_text : a.annotations()) {
389 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
390 // Annotation wide enough to fit a label? Don't put it in a block then
391 if (w <= a_width) {
392 a_is_separate = true;
393 break;
394 }
395 }
396 }
50631798 397
bdc2a99b
SA
398 // Were the previous and this annotation more than a pixel apart?
399 if ((abs(delta) > 1) || a_is_separate) {
400 // Block was broken, draw annotations that form the current block
401 if (a_block.size() == 1) {
aee9dcf3
SA
402 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
403 row_title_width);
bdc2a99b 404 }
50631798 405 else
bdc2a99b 406 draw_annotation_block(a_block, p, h, y, base_colour);
50631798
SA
407
408 a_block.clear();
409 }
410
bdc2a99b 411 if (a_is_separate) {
aee9dcf3 412 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
bdc2a99b
SA
413 // Next annotation must start a new block. delta will be > 1
414 // because we set p_end to INT_MIN but that's okay since
415 // a_block will be empty, so nothing will be drawn
416 p_end = INT_MIN;
417 } else {
418 a_block.push_back(a);
419 p_end = a_end;
420 }
50631798
SA
421 }
422
423 if (a_block.size() == 1)
aee9dcf3
SA
424 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
425 row_title_width);
50631798 426 else
33707990 427 draw_annotation_block(a_block, p, h, y, base_colour);
50631798
SA
428}
429
287d607f 430void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
5b5fa4da 431 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
aee9dcf3 432 size_t base_colour, int row_title_width) const
06e810f2 433{
53e35b2d
JH
434 double samples_per_pixel, pixels_offset;
435 tie(pixels_offset, samples_per_pixel) =
436 get_pixels_offset_samples_per_pixel();
7f8517f6 437
06e810f2
JH
438 const double start = a.start_sample() / samples_per_pixel -
439 pixels_offset;
c063290a 440 const double end = a.end_sample() / samples_per_pixel - pixels_offset;
287d607f
JH
441
442 const size_t colour = (base_colour + a.format()) % countof(Colours);
f765c3db
SA
443 p.setPen(OutlineColours[colour]);
444 p.setBrush(Colours[colour]);
06e810f2 445
3eb29afd 446 if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
06e810f2
JH
447 return;
448
449 if (a.start_sample() == a.end_sample())
f765c3db 450 draw_instant(a, p, h, start, y);
06e810f2 451 else
c063290a 452 draw_range(a, p, h, start, end, y, pp, row_title_width);
06e810f2
JH
453}
454
33707990
SA
455void DecodeTrace::draw_annotation_block(
456 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
457 int y, size_t base_colour) const
50631798 458{
33707990
SA
459 using namespace pv::data::decode;
460
bdc2a99b
SA
461 if (annotations.empty())
462 return;
463
50631798
SA
464 double samples_per_pixel, pixels_offset;
465 tie(pixels_offset, samples_per_pixel) =
466 get_pixels_offset_samples_per_pixel();
467
33707990
SA
468 const double start = annotations.front().start_sample() /
469 samples_per_pixel - pixels_offset;
470 const double end = annotations.back().end_sample() /
471 samples_per_pixel - pixels_offset;
472
473 const double top = y + .5 - h / 2;
474 const double bottom = y + .5 + h / 2;
33707990
SA
475
476 const size_t colour = (base_colour + annotations.front().format()) %
477 countof(Colours);
478
479 // Check if all annotations are of the same type (i.e. we can use one color)
480 // or if we should use a neutral color (i.e. gray)
8c0302f5 481 const int format = annotations.front().format();
6f925ba9 482 const bool single_format = all_of(
8c0302f5
JH
483 annotations.begin(), annotations.end(),
484 [&](const Annotation &a) { return a.format() == format; });
50631798 485
33707990
SA
486 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
487 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
488 Qt::Dense4Pattern));
086f4df5 489 p.drawRoundedRect(
c063290a 490 QRectF(start, top, end - start, bottom - top), h / 4, h / 4);
50631798
SA
491}
492
06e810f2 493void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
f765c3db 494 int h, double x, int y) const
06e810f2
JH
495{
496 const QString text = a.annotations().empty() ?
497 QString() : a.annotations().back();
ea86bc4d 498 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
06e810f2
JH
499 0.0) + h;
500 const QRectF rect(x - w / 2, y - h / 2, w, h);
501
06e810f2
JH
502 p.drawRoundedRect(rect, h / 2, h / 2);
503
2a56e448 504 p.setPen(Qt::black);
06e810f2
JH
505 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
506}
507
508void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
f765c3db
SA
509 int h, double start, double end, int y, const ViewItemPaintParams &pp,
510 int row_title_width) const
06e810f2
JH
511{
512 const double top = y + .5 - h / 2;
513 const double bottom = y + .5 + h / 2;
514 const vector<QString> annotations = a.annotations();
515
06e810f2 516 // If the two ends are within 1 pixel, draw a vertical line
2ad82c2e 517 if (start + 1.0 > end) {
06e810f2
JH
518 p.drawLine(QPointF(start, top), QPointF(start, bottom));
519 return;
520 }
521
522 const double cap_width = min((end - start) / 4, EndCapWidth);
523
524 QPointF pts[] = {
525 QPointF(start, y + .5f),
526 QPointF(start + cap_width, top),
527 QPointF(end - cap_width, top),
528 QPointF(end, y + .5f),
529 QPointF(end - cap_width, bottom),
530 QPointF(start + cap_width, bottom)
531 };
532
533 p.drawConvexPolygon(pts, countof(pts));
534
535 if (annotations.empty())
536 return;
537
7352be72
SA
538 const int ann_start = start + cap_width;
539 const int ann_end = end - cap_width;
540
6f925ba9
UH
541 const int real_start = max(ann_start, pp.left() + row_title_width);
542 const int real_end = min(ann_end, pp.right());
7352be72
SA
543 const int real_width = real_end - real_start;
544
545 QRectF rect(real_start, y - h / 2, real_width, h);
0f290e9b
JH
546 if (rect.width() <= 4)
547 return;
548
2a56e448 549 p.setPen(Qt::black);
06e810f2
JH
550
551 // Try to find an annotation that will fit
552 QString best_annotation;
553 int best_width = 0;
554
d9aecf1f 555 for (const QString &a : annotations) {
06e810f2
JH
556 const int w = p.boundingRect(QRectF(), 0, a).width();
557 if (w <= rect.width() && w > best_width)
558 best_annotation = a, best_width = w;
559 }
560
561 if (best_annotation.isEmpty())
562 best_annotation = annotations.back();
563
564 // If not ellide the last in the list
565 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
566 best_annotation, Qt::ElideRight, rect.width()));
567}
568
b9329558 569void DecodeTrace::draw_error(QPainter &p, const QString &message,
5b5fa4da 570 const ViewItemPaintParams &pp)
ad50ac1a 571{
be9e7b4b 572 const int y = get_visual_y();
ad50ac1a
JH
573
574 p.setPen(ErrorBgColour.darker());
575 p.setBrush(ErrorBgColour);
576
577 const QRectF bounding_rect =
3eb29afd 578 QRectF(pp.width(), INT_MIN / 2 + y, pp.width(), INT_MAX);
ad50ac1a
JH
579 const QRectF text_rect = p.boundingRect(bounding_rect,
580 Qt::AlignCenter, message);
581 const float r = text_rect.height() / 4;
582
583 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
584 Qt::AbsoluteSize);
585
2a56e448 586 p.setPen(Qt::black);
ad50ac1a
JH
587 p.drawText(text_rect, message);
588}
589
5dfeb70f 590void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left,
7f8517f6 591 int right) const
5dfeb70f
JH
592{
593 using namespace pv::data;
594 using pv::data::decode::Decoder;
595
53e35b2d
JH
596 double samples_per_pixel, pixels_offset;
597
6f925ba9 598 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726
SA
599
600 assert(decoder_stack);
5dfeb70f
JH
601
602 shared_ptr<Logic> data;
04394ded 603 shared_ptr<data::SignalBase> signalbase;
5dfeb70f 604
bb7dd726 605 const list< shared_ptr<Decoder> > &stack = decoder_stack->stack();
5dfeb70f 606
6ac6242b 607 // We get the logic data of the first channel in the list.
5dfeb70f 608 // This works because we are currently assuming all
f3d66e52 609 // LogicSignals have the same data/segment
d9aecf1f 610 for (const shared_ptr<Decoder> &dec : stack)
8bd26d8b 611 if (dec && !dec->channels().empty() &&
04394ded
SA
612 ((signalbase = (*dec->channels().begin()).second)) &&
613 ((data = signalbase->logic_data())))
5dfeb70f
JH
614 break;
615
f3d66e52 616 if (!data || data->logic_segments().empty())
5dfeb70f
JH
617 return;
618
c063290a 619 const shared_ptr<LogicSegment> segment = data->logic_segments().front();
f3d66e52
JH
620 assert(segment);
621 const int64_t sample_count = (int64_t)segment->get_sample_count();
5dfeb70f
JH
622 if (sample_count == 0)
623 return;
624
bb7dd726 625 const int64_t samples_decoded = decoder_stack->samples_decoded();
5dfeb70f
JH
626 if (sample_count == samples_decoded)
627 return;
628
be9e7b4b 629 const int y = get_visual_y();
7f8517f6 630
53e35b2d
JH
631 tie(pixels_offset, samples_per_pixel) =
632 get_pixels_offset_samples_per_pixel();
7f8517f6 633
5dfeb70f
JH
634 const double start = max(samples_decoded /
635 samples_per_pixel - pixels_offset, left - 1.0);
636 const double end = min(sample_count / samples_per_pixel -
637 pixels_offset, right + 1.0);
c063290a 638 const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
5dfeb70f
JH
639
640 p.setPen(QPen(Qt::NoPen));
641 p.setBrush(Qt::white);
642 p.drawRect(no_decode_rect);
643
644 p.setPen(NoDecodeColour);
645 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
646 p.drawRect(no_decode_rect);
647}
648
53e35b2d 649pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
7f8517f6 650{
6f925ba9 651 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726 652
8dbbc7f0 653 assert(owner_);
bb7dd726 654 assert(decoder_stack);
7f8517f6 655
8dbbc7f0 656 const View *view = owner_->view();
eae6e30a
JH
657 assert(view);
658
659 const double scale = view->scale();
7f8517f6
SA
660 assert(scale > 0);
661
53e35b2d 662 const double pixels_offset =
bb7dd726 663 ((view->offset() - decoder_stack->start_time()) / scale).convert_to<double>();
7f8517f6 664
bb7dd726 665 double samplerate = decoder_stack->samplerate();
7f8517f6
SA
666
667 // Show sample rate as 1Hz when it is unknown
668 if (samplerate == 0.0)
669 samplerate = 1.0;
670
53e35b2d 671 return make_pair(pixels_offset, samplerate * scale);
7f8517f6
SA
672}
673
db1bf6bf
JH
674pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
675 int x_start, int x_end) const
7f8517f6 676{
53e35b2d
JH
677 double samples_per_pixel, pixels_offset;
678 tie(pixels_offset, samples_per_pixel) =
679 get_pixels_offset_samples_per_pixel();
7f8517f6 680
db1bf6bf
JH
681 const uint64_t start = (uint64_t)max(
682 (x_start + pixels_offset) * samples_per_pixel, 0.0);
683 const uint64_t end = (uint64_t)max(
684 (x_end + pixels_offset) * samples_per_pixel, 0.0);
7f8517f6
SA
685
686 return make_pair(start, end);
687}
688
117cdea3 689int DecodeTrace::get_row_at_point(const QPoint &point)
e2f90c50 690{
8dbbc7f0 691 if (!row_height_)
117cdea3 692 return -1;
e2f90c50 693
99029fda
SA
694 const int y = (point.y() - get_visual_y() + row_height_ / 2);
695
696 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
697 if (y < 0)
698 return -1;
699
700 const int row = y / row_height_;
701
702 if (row >= (int)visible_rows_.size())
117cdea3 703 return -1;
e2f90c50 704
117cdea3 705 return row;
e2f90c50
SA
706}
707
117cdea3 708const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
e2f90c50
SA
709{
710 using namespace pv::data::decode;
711
117cdea3
JH
712 if (!enabled())
713 return QString();
e2f90c50 714
117cdea3
JH
715 const pair<uint64_t, uint64_t> sample_range =
716 get_sample_range(point.x(), point.x() + 1);
717 const int row = get_row_at_point(point);
718 if (row < 0)
719 return QString();
e2f90c50
SA
720
721 vector<pv::data::decode::Annotation> annotations;
722
6f925ba9 723 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726
SA
724
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
6f925ba9 831 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726 832
7491a29f 833 // Add the options
3cc9ad7b 834 shared_ptr<binding::Decoder> binding(
bb7dd726 835 new binding::Decoder(decoder_stack, dec));
204bae45 836 binding->add_properties_to_form(decoder_form, true);
7491a29f 837
8dbbc7f0 838 bindings_.push_back(binding);
204bae45
JH
839
840 form->addRow(group);
8dbbc7f0 841 decoder_forms_.push_back(group);
7491a29f
JH
842}
843
6ac6242b 844QComboBox* DecodeTrace::create_channel_selector(
7491a29f 845 QWidget *parent, const shared_ptr<data::decode::Decoder> &dec,
8bd26d8b 846 const srd_channel *const pdch)
4e5a4405 847{
7491a29f
JH
848 assert(dec);
849
47e9e7bb 850 const auto sigs(session_.signalbases());
78b0af3e 851
47e9e7bb 852 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
6f925ba9 853 sort(sig_list.begin(), sig_list.end(),
47e9e7bb
SA
854 [](const shared_ptr<data::SignalBase> &a,
855 const shared_ptr<data::SignalBase> &b) {
856 return strnatcasecmp(a->name().toStdString(),
857 b->name().toStdString()) < 0; });
4e5a4405 858
6ac6242b 859 const auto channel_iter = dec->channels().find(pdch);
4e5a4405
JH
860
861 QComboBox *selector = new QComboBox(parent);
862
4c60462b 863 selector->addItem("-", qVariantFromValue((void*)nullptr));
4e5a4405 864
6ac6242b 865 if (channel_iter == dec->channels().end())
4e5a4405
JH
866 selector->setCurrentIndex(0);
867
47e9e7bb
SA
868 for (const shared_ptr<data::SignalBase> &b : sig_list) {
869 assert(b);
79c4a9c8 870 if (b->logic_data() && b->enabled()) {
47e9e7bb
SA
871 selector->addItem(b->name(),
872 qVariantFromValue((void*)b.get()));
5da5d081
TS
873
874 if (channel_iter != dec->channels().end() &&
47e9e7bb 875 (*channel_iter).second == b)
78b0af3e
JH
876 selector->setCurrentIndex(
877 selector->count() - 1);
4e5a4405
JH
878 }
879 }
880
881 return selector;
882}
883
6ac6242b 884void DecodeTrace::commit_decoder_channels(shared_ptr<data::decode::Decoder> &dec)
4e5a4405 885{
7491a29f 886 assert(dec);
4e5a4405 887
04394ded 888 map<const srd_channel*, shared_ptr<data::SignalBase> > channel_map;
c3a740dd 889
47e9e7bb
SA
890 const unordered_set< shared_ptr<data::SignalBase> >
891 sigs(session_.signalbases());
4e5a4405 892
2ad82c2e 893 for (const ChannelSelector &s : channel_selectors_) {
f3290553 894 if (s.decoder_ != dec)
7491a29f
JH
895 break;
896
04394ded
SA
897 const data::SignalBase *const selection =
898 (data::SignalBase*)s.combo_->itemData(
8dbbc7f0 899 s.combo_->currentIndex()).value<void*>();
4e5a4405 900
47e9e7bb
SA
901 for (shared_ptr<data::SignalBase> sig : sigs)
902 if (sig.get() == selection) {
903 channel_map[s.pdch_] = sig;
4e5a4405
JH
904 break;
905 }
906 }
907
6ac6242b 908 dec->set_channels(channel_map);
7491a29f
JH
909}
910
6ac6242b 911void DecodeTrace::commit_channels()
7491a29f 912{
6f925ba9 913 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726
SA
914
915 assert(decoder_stack);
916 for (shared_ptr<data::decode::Decoder> dec : decoder_stack->stack())
6ac6242b 917 commit_decoder_channels(dec);
7491a29f 918
bb7dd726 919 decoder_stack->begin_decode();
4e5a4405
JH
920}
921
b9329558 922void DecodeTrace::on_new_decode_data()
9cef9567 923{
8dbbc7f0 924 if (owner_)
6e2c3c85 925 owner_->row_item_appearance_changed(false, true);
9cef9567
JH
926}
927
b9329558 928void DecodeTrace::delete_pressed()
5ed1adf5
JH
929{
930 on_delete();
931}
932
b9329558 933void DecodeTrace::on_delete()
c51482b3 934{
bb7dd726 935 session_.remove_decode_signal(base_);
c51482b3
JH
936}
937
6ac6242b 938void DecodeTrace::on_channel_selected(int)
4e5a4405 939{
6ac6242b 940 commit_channels();
4e5a4405
JH
941}
942
7491a29f
JH
943void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
944{
6f925ba9 945 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726 946
7491a29f 947 assert(decoder);
bb7dd726 948 assert(decoder_stack);
067bb624 949 decoder_stack->push(make_shared<data::decode::Decoder>(decoder));
bb7dd726 950 decoder_stack->begin_decode();
37fd11b1
JH
951
952 create_popup_form();
7491a29f
JH
953}
954
613d097c
JH
955void DecodeTrace::on_delete_decoder(int index)
956{
6f925ba9 957 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726
SA
958
959 decoder_stack->remove(index);
613d097c
JH
960
961 // Update the popup
c063290a 962 create_popup_form();
613d097c 963
bb7dd726 964 decoder_stack->begin_decode();
613d097c
JH
965}
966
dd048a7e
JH
967void DecodeTrace::on_show_hide_decoder(int index)
968{
969 using pv::data::decode::Decoder;
970
6f925ba9 971 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726
SA
972
973 const list< shared_ptr<Decoder> > stack(decoder_stack->stack());
dd048a7e
JH
974
975 // Find the decoder in the stack
f46e495e 976 auto iter = stack.cbegin();
f3290553 977 for (int i = 0; i < index; i++, iter++)
dd048a7e
JH
978 assert(iter != stack.end());
979
980 shared_ptr<Decoder> dec = *iter;
981 assert(dec);
982
983 const bool show = !dec->shown();
984 dec->show(show);
985
8dbbc7f0
JH
986 assert(index < (int)decoder_forms_.size());
987 decoder_forms_[index]->set_decoder_visible(show);
dd048a7e 988
8dbbc7f0 989 if (owner_)
6e2c3c85 990 owner_->row_item_appearance_changed(false, true);
dd048a7e
JH
991}
992
f4e57597
SA
993} // namespace TraceView
994} // namespace views
55d3603d 995} // namespace pv