]> sigrok.org Git - pulseview.git/blame - pv/views/trace/decodetrace.cpp
Move more functionality into DecodeSignal
[pulseview.git] / pv / views / trace / 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 41#include "decodetrace.hpp"
1573bf16
SA
42#include "view.hpp"
43#include "viewport.hpp"
2acdb232 44
1cc1c8de 45#include <pv/globalsettings.hpp>
ad908057
SA
46#include <pv/session.hpp>
47#include <pv/strnatcmp.hpp>
48#include <pv/data/decodesignal.hpp>
aca9aa83 49#include <pv/data/decode/annotation.hpp>
2acdb232
JH
50#include <pv/data/decode/decoder.hpp>
51#include <pv/data/logic.hpp>
f3d66e52 52#include <pv/data/logicsegment.hpp>
2acdb232
JH
53#include <pv/widgets/decodergroupbox.hpp>
54#include <pv/widgets/decodermenu.hpp>
119aff65 55
6f925ba9 56using std::all_of;
819f4c25 57using std::list;
7f8517f6 58using std::make_pair;
819f4c25 59using std::max;
a5d93c27 60using std::make_pair;
819f4c25
JH
61using std::map;
62using std::min;
6f925ba9 63using std::out_of_range;
7f8517f6 64using std::pair;
f9abf97e 65using std::shared_ptr;
067bb624 66using std::make_shared;
53e35b2d 67using std::tie;
78b0af3e 68using std::unordered_set;
819f4c25 69using std::vector;
55d3603d 70
ecd07c20
SA
71using pv::data::decode::Annotation;
72using pv::data::decode::Row;
9f97b357
SA
73using pv::data::DecodeChannel;
74using pv::data::DecodeSignal;
ecd07c20 75
55d3603d 76namespace pv {
f4e57597 77namespace views {
1573bf16 78namespace trace {
55d3603d 79
b9329558 80const QColor DecodeTrace::DecodeColours[4] = {
06bb4e6a
JH
81 QColor(0xEF, 0x29, 0x29), // Red
82 QColor(0xFC, 0xE9, 0x4F), // Yellow
83 QColor(0x8A, 0xE2, 0x34), // Green
84 QColor(0x72, 0x9F, 0xCF) // Blue
85};
86
b9329558 87const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
5dfeb70f 88const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
ad50ac1a 89
88908838 90const int DecodeTrace::ArrowSize = 4;
06e810f2 91const double DecodeTrace::EndCapWidth = 5;
aee9dcf3 92const int DecodeTrace::RowTitleMargin = 10;
06e810f2
JH
93const int DecodeTrace::DrawPadding = 100;
94
287d607f
JH
95const QColor DecodeTrace::Colours[16] = {
96 QColor(0xEF, 0x29, 0x29),
97 QColor(0xF6, 0x6A, 0x32),
98 QColor(0xFC, 0xAE, 0x3E),
99 QColor(0xFB, 0xCA, 0x47),
100 QColor(0xFC, 0xE9, 0x4F),
101 QColor(0xCD, 0xF0, 0x40),
102 QColor(0x8A, 0xE2, 0x34),
103 QColor(0x4E, 0xDC, 0x44),
104 QColor(0x55, 0xD7, 0x95),
105 QColor(0x64, 0xD1, 0xD2),
106 QColor(0x72, 0x9F, 0xCF),
107 QColor(0xD4, 0x76, 0xC4),
108 QColor(0x9D, 0x79, 0xB9),
109 QColor(0xAD, 0x7F, 0xA8),
110 QColor(0xC2, 0x62, 0x9B),
111 QColor(0xD7, 0x47, 0x6F)
112};
113
114const QColor DecodeTrace::OutlineColours[16] = {
115 QColor(0x77, 0x14, 0x14),
116 QColor(0x7B, 0x35, 0x19),
117 QColor(0x7E, 0x57, 0x1F),
118 QColor(0x7D, 0x65, 0x23),
119 QColor(0x7E, 0x74, 0x27),
120 QColor(0x66, 0x78, 0x20),
121 QColor(0x45, 0x71, 0x1A),
122 QColor(0x27, 0x6E, 0x22),
123 QColor(0x2A, 0x6B, 0x4A),
124 QColor(0x32, 0x68, 0x69),
125 QColor(0x39, 0x4F, 0x67),
126 QColor(0x6A, 0x3B, 0x62),
127 QColor(0x4E, 0x3C, 0x5C),
128 QColor(0x56, 0x3F, 0x54),
129 QColor(0x61, 0x31, 0x4D),
130 QColor(0x6B, 0x23, 0x37)
06e810f2
JH
131};
132
2b81ae46 133DecodeTrace::DecodeTrace(pv::Session &session,
bb7dd726 134 shared_ptr<data::SignalBase> signalbase, int index) :
bf0edd2b 135 Trace(signalbase),
8dbbc7f0 136 session_(session),
8dbbc7f0 137 row_height_(0),
eee89ff8 138 max_visible_rows_(0),
8dbbc7f0
JH
139 delete_mapper_(this),
140 show_hide_mapper_(this)
55d3603d 141{
ad908057 142 decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
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
455bc29a 148 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
9cef9567 149
ad908057
SA
150 connect(decode_signal_.get(), SIGNAL(new_annotations()),
151 this, SLOT(on_new_annotations()));
9f97b357
SA
152 connect(decode_signal_.get(), SIGNAL(channels_updated()),
153 this, SLOT(on_channels_updated()));
154
8dbbc7f0 155 connect(&delete_mapper_, SIGNAL(mapped(int)),
613d097c 156 this, SLOT(on_delete_decoder(int)));
8dbbc7f0 157 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
dd048a7e 158 this, SLOT(on_show_hide_decoder(int)));
55d3603d
JH
159}
160
b9329558 161bool DecodeTrace::enabled() const
55d3603d
JH
162{
163 return true;
164}
165
6f925ba9 166shared_ptr<data::SignalBase> DecodeTrace::base() const
b6b267bb 167{
bb7dd726 168 return base_;
b6b267bb
JH
169}
170
a5d93c27
JH
171pair<int, int> DecodeTrace::v_extents() const
172{
5b5fa4da 173 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
796e1360 174
e40a79cb
SA
175 // Make an empty decode trace appear symmetrical
176 const int row_count = max(1, max_visible_rows_);
177
178 return make_pair(-row_height, row_height * row_count);
a5d93c27
JH
179}
180
60938e04 181void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
fe08b6e8 182{
3eb29afd 183 Trace::paint_back(p, pp);
97904bf7 184 paint_axis(p, pp, get_visual_y());
fe08b6e8
JH
185}
186
60938e04 187void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
55d3603d 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
ecd07c20 193 const QString err = decode_signal_->error_message();
2ad82c2e 194 if (!err.isEmpty()) {
3eb29afd
JH
195 draw_unresolved_period(
196 p, annotation_height, pp.left(), pp.right());
197 draw_error(p, err, pp);
5dfeb70f
JH
198 return;
199 }
200
cd0c558b
SA
201 // Set default pen to allow for text width calculation
202 p.setPen(Qt::black);
203
f9101a91 204 // Iterate through the rows
be9e7b4b 205 int y = get_visual_y();
3eb29afd
JH
206 pair<uint64_t, uint64_t> sample_range = get_sample_range(
207 pp.left(), pp.right());
5dfeb70f 208
ecd07c20 209 const vector<Row> rows = decode_signal_->visible_rows();
7f8517f6 210
8dbbc7f0 211 visible_rows_.clear();
4fb5fb99 212 for (const Row& row : rows) {
aee9dcf3
SA
213 // Cache the row title widths
214 int row_title_width;
215 try {
216 row_title_width = row_title_widths_.at(row);
6f925ba9 217 } catch (out_of_range) {
aee9dcf3
SA
218 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
219 RowTitleMargin;
220 row_title_widths_[row] = w;
221 row_title_width = w;
222 }
223
224 // Determine the row's color
287d607f
JH
225 size_t base_colour = 0x13579BDF;
226 boost::hash_combine(base_colour, this);
227 boost::hash_combine(base_colour, row.decoder());
228 boost::hash_combine(base_colour, row.row());
229 base_colour >>= 16;
230
f9101a91 231 vector<Annotation> annotations;
ecd07c20 232 decode_signal_->get_annotation_subset(annotations, row,
7f8517f6 233 sample_range.first, sample_range.second);
f9101a91 234 if (!annotations.empty()) {
50631798 235 draw_annotations(annotations, p, annotation_height, pp, y,
aee9dcf3 236 base_colour, row_title_width);
50631798 237
8dbbc7f0 238 y += row_height_;
88908838 239
4fb5fb99 240 visible_rows_.push_back(row);
f9101a91 241 }
7e674e43 242 }
5dfeb70f 243
f9101a91 244 // Draw the hatching
3eb29afd 245 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
eee89ff8 246
a303c2d8
SA
247 if ((int)visible_rows_.size() > max_visible_rows_)
248 owner_->extents_changed(false, true);
249
eee89ff8 250 // Update the maximum row count if needed
6f925ba9 251 max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
55d3603d
JH
252}
253
60938e04 254void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
88908838 255{
8dbbc7f0 256 assert(row_height_);
88908838 257
2ad82c2e 258 for (size_t i = 0; i < visible_rows_.size(); i++) {
8dbbc7f0 259 const int y = i * row_height_ + get_visual_y();
88908838
JH
260
261 p.setPen(QPen(Qt::NoPen));
262 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
263
2ad82c2e 264 if (i != 0) {
88908838 265 const QPointF points[] = {
3eb29afd
JH
266 QPointF(pp.left(), y - ArrowSize),
267 QPointF(pp.left() + ArrowSize, y),
268 QPointF(pp.left(), y + ArrowSize)
88908838
JH
269 };
270 p.drawPolygon(points, countof(points));
271 }
272
3eb29afd
JH
273 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
274 pp.right() - pp.left(), row_height_);
8dbbc7f0 275 const QString h(visible_rows_[i].title());
88908838
JH
276 const int f = Qt::AlignLeft | Qt::AlignVCenter |
277 Qt::TextDontClip;
278
279 // Draw the outline
280 p.setPen(QApplication::palette().color(QPalette::Base));
281 for (int dx = -1; dx <= 1; dx++)
282 for (int dy = -1; dy <= 1; dy++)
283 if (dx != 0 && dy != 0)
284 p.drawText(r.translated(dx, dy), f, h);
285
286 // Draw the text
287 p.setPen(QApplication::palette().color(QPalette::WindowText));
288 p.drawText(r, f, h);
289 }
290}
291
b9329558 292void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
4e5a4405 293{
613d097c
JH
294 using pv::data::decode::Decoder;
295
4e5a4405 296 assert(form);
4e5a4405 297
7491a29f 298 // Add the standard options
4e5a4405
JH
299 Trace::populate_popup_form(parent, form);
300
7491a29f 301 // Add the decoder options
8dbbc7f0 302 bindings_.clear();
9f97b357
SA
303 channel_id_map_.clear();
304 init_state_map_.clear();
8dbbc7f0 305 decoder_forms_.clear();
4e5a4405 306
ff83d980 307 const list< shared_ptr<Decoder> > &stack =
ecd07c20 308 decode_signal_->decoder_stack_list();
4e5a4405 309
2ad82c2e 310 if (stack.empty()) {
5069084a
JH
311 QLabel *const l = new QLabel(
312 tr("<p><i>No decoders in the stack</i></p>"));
313 l->setAlignment(Qt::AlignCenter);
314 form->addRow(l);
2ad82c2e 315 } else {
f46e495e 316 auto iter = stack.cbegin();
5069084a
JH
317 for (int i = 0; i < (int)stack.size(); i++, iter++) {
318 shared_ptr<Decoder> dec(*iter);
319 create_decoder_form(i, dec, parent, form);
320 }
321
322 form->addRow(new QLabel(
8bd26d8b 323 tr("<i>* Required channels</i>"), parent));
5069084a 324 }
4e5a4405 325
ce94e4fd 326 // Add stacking button
ce94e4fd
JH
327 pv::widgets::DecoderMenu *const decoder_menu =
328 new pv::widgets::DecoderMenu(parent);
7491a29f
JH
329 connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
330 this, SLOT(on_stack_decoder(srd_decoder*)));
331
332 QPushButton *const stack_button =
333 new QPushButton(tr("Stack Decoder"), parent);
ce94e4fd 334 stack_button->setMenu(decoder_menu);
b14a9371 335 stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
ce94e4fd
JH
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
3082ee93
JH
486 const QRectF rect(start, top, end - start, bottom - top);
487 const int r = h / 4;
488
489 p.setPen(QPen(Qt::NoPen));
490 p.setBrush(Qt::white);
491 p.drawRoundedRect(rect, r, r);
492
33707990
SA
493 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
494 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
495 Qt::Dense4Pattern));
3082ee93 496 p.drawRoundedRect(rect, r, r);
50631798
SA
497}
498
06e810f2 499void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
f765c3db 500 int h, double x, int y) const
06e810f2
JH
501{
502 const QString text = a.annotations().empty() ?
503 QString() : a.annotations().back();
ea86bc4d 504 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
06e810f2
JH
505 0.0) + h;
506 const QRectF rect(x - w / 2, y - h / 2, w, h);
507
06e810f2
JH
508 p.drawRoundedRect(rect, h / 2, h / 2);
509
2a56e448 510 p.setPen(Qt::black);
06e810f2
JH
511 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
512}
513
514void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
f765c3db
SA
515 int h, double start, double end, int y, const ViewItemPaintParams &pp,
516 int row_title_width) const
06e810f2
JH
517{
518 const double top = y + .5 - h / 2;
519 const double bottom = y + .5 + h / 2;
520 const vector<QString> annotations = a.annotations();
521
06e810f2 522 // If the two ends are within 1 pixel, draw a vertical line
2ad82c2e 523 if (start + 1.0 > end) {
06e810f2
JH
524 p.drawLine(QPointF(start, top), QPointF(start, bottom));
525 return;
526 }
527
528 const double cap_width = min((end - start) / 4, EndCapWidth);
529
530 QPointF pts[] = {
531 QPointF(start, y + .5f),
532 QPointF(start + cap_width, top),
533 QPointF(end - cap_width, top),
534 QPointF(end, y + .5f),
535 QPointF(end - cap_width, bottom),
536 QPointF(start + cap_width, bottom)
537 };
538
539 p.drawConvexPolygon(pts, countof(pts));
540
541 if (annotations.empty())
542 return;
543
7352be72
SA
544 const int ann_start = start + cap_width;
545 const int ann_end = end - cap_width;
546
6f925ba9
UH
547 const int real_start = max(ann_start, pp.left() + row_title_width);
548 const int real_end = min(ann_end, pp.right());
7352be72
SA
549 const int real_width = real_end - real_start;
550
551 QRectF rect(real_start, y - h / 2, real_width, h);
0f290e9b
JH
552 if (rect.width() <= 4)
553 return;
554
2a56e448 555 p.setPen(Qt::black);
06e810f2
JH
556
557 // Try to find an annotation that will fit
558 QString best_annotation;
559 int best_width = 0;
560
d9aecf1f 561 for (const QString &a : annotations) {
06e810f2
JH
562 const int w = p.boundingRect(QRectF(), 0, a).width();
563 if (w <= rect.width() && w > best_width)
564 best_annotation = a, best_width = w;
565 }
566
567 if (best_annotation.isEmpty())
568 best_annotation = annotations.back();
569
570 // If not ellide the last in the list
571 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
572 best_annotation, Qt::ElideRight, rect.width()));
573}
574
b9329558 575void DecodeTrace::draw_error(QPainter &p, const QString &message,
5b5fa4da 576 const ViewItemPaintParams &pp)
ad50ac1a 577{
be9e7b4b 578 const int y = get_visual_y();
ad50ac1a
JH
579
580 p.setPen(ErrorBgColour.darker());
581 p.setBrush(ErrorBgColour);
582
583 const QRectF bounding_rect =
a998be27 584 QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
ad50ac1a
JH
585 const QRectF text_rect = p.boundingRect(bounding_rect,
586 Qt::AlignCenter, message);
587 const float r = text_rect.height() / 4;
588
589 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
590 Qt::AbsoluteSize);
591
2a56e448 592 p.setPen(Qt::black);
ad50ac1a
JH
593 p.drawText(text_rect, message);
594}
595
ff83d980 596void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, 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
ff83d980 603 const int64_t sample_count = decode_signal_->sample_count();
5dfeb70f
JH
604 if (sample_count == 0)
605 return;
606
ff83d980 607 const int64_t samples_decoded = decode_signal_->samples_decoded();
5dfeb70f
JH
608 if (sample_count == samples_decoded)
609 return;
610
be9e7b4b 611 const int y = get_visual_y();
7f8517f6 612
ff83d980 613 tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
7f8517f6 614
5dfeb70f
JH
615 const double start = max(samples_decoded /
616 samples_per_pixel - pixels_offset, left - 1.0);
617 const double end = min(sample_count / samples_per_pixel -
618 pixels_offset, right + 1.0);
c063290a 619 const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
5dfeb70f
JH
620
621 p.setPen(QPen(Qt::NoPen));
622 p.setBrush(Qt::white);
623 p.drawRect(no_decode_rect);
624
625 p.setPen(NoDecodeColour);
626 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
627 p.drawRect(no_decode_rect);
628}
629
53e35b2d 630pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
7f8517f6 631{
8dbbc7f0 632 assert(owner_);
7f8517f6 633
8dbbc7f0 634 const View *view = owner_->view();
eae6e30a
JH
635 assert(view);
636
637 const double scale = view->scale();
7f8517f6
SA
638 assert(scale > 0);
639
53e35b2d 640 const double pixels_offset =
ff83d980 641 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
7f8517f6 642
ff83d980 643 double samplerate = decode_signal_->samplerate();
7f8517f6
SA
644
645 // Show sample rate as 1Hz when it is unknown
646 if (samplerate == 0.0)
647 samplerate = 1.0;
648
53e35b2d 649 return make_pair(pixels_offset, samplerate * scale);
7f8517f6
SA
650}
651
db1bf6bf
JH
652pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
653 int x_start, int x_end) const
7f8517f6 654{
53e35b2d
JH
655 double samples_per_pixel, pixels_offset;
656 tie(pixels_offset, samples_per_pixel) =
657 get_pixels_offset_samples_per_pixel();
7f8517f6 658
db1bf6bf
JH
659 const uint64_t start = (uint64_t)max(
660 (x_start + pixels_offset) * samples_per_pixel, 0.0);
661 const uint64_t end = (uint64_t)max(
662 (x_end + pixels_offset) * samples_per_pixel, 0.0);
7f8517f6
SA
663
664 return make_pair(start, end);
665}
666
117cdea3 667int DecodeTrace::get_row_at_point(const QPoint &point)
e2f90c50 668{
8dbbc7f0 669 if (!row_height_)
117cdea3 670 return -1;
e2f90c50 671
99029fda
SA
672 const int y = (point.y() - get_visual_y() + row_height_ / 2);
673
674 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
675 if (y < 0)
676 return -1;
677
678 const int row = y / row_height_;
679
680 if (row >= (int)visible_rows_.size())
117cdea3 681 return -1;
e2f90c50 682
117cdea3 683 return row;
e2f90c50
SA
684}
685
117cdea3 686const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
e2f90c50
SA
687{
688 using namespace pv::data::decode;
689
117cdea3
JH
690 if (!enabled())
691 return QString();
e2f90c50 692
117cdea3
JH
693 const pair<uint64_t, uint64_t> sample_range =
694 get_sample_range(point.x(), point.x() + 1);
695 const int row = get_row_at_point(point);
696 if (row < 0)
697 return QString();
e2f90c50
SA
698
699 vector<pv::data::decode::Annotation> annotations;
700
ff83d980 701 decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
e2f90c50
SA
702 sample_range.first, sample_range.second);
703
704 return (annotations.empty()) ?
705 QString() : annotations[0].annotations().front();
706}
707
117cdea3
JH
708void DecodeTrace::hover_point_changed()
709{
8dbbc7f0 710 assert(owner_);
eae6e30a 711
8dbbc7f0 712 const View *const view = owner_->view();
eae6e30a
JH
713 assert(view);
714
715 QPoint hp = view->hover_point();
117cdea3 716 QString ann = get_annotation_at_point(hp);
e2f90c50 717
eae6e30a 718 assert(view);
e2f90c50 719
8b9df0ad 720 if (!row_height_ || ann.isEmpty()) {
ebdfa094 721 QToolTip::hideText();
117cdea3
JH
722 return;
723 }
6e6881e2 724
117cdea3 725 const int hover_row = get_row_at_point(hp);
6e6881e2 726
117cdea3
JH
727 QFontMetrics m(QToolTip::font());
728 const QRect text_size = m.boundingRect(QRect(), 0, ann);
e2f90c50 729
117cdea3
JH
730 // This is OS-specific and unfortunately we can't query it, so
731 // use an approximation to at least try to minimize the error.
732 const int padding = 8;
6e6881e2 733
117cdea3
JH
734 // Make sure the tool tip doesn't overlap with the mouse cursor.
735 // If it did, the tool tip would constantly hide and re-appear.
736 // We also push it up by one row so that it appears above the
737 // decode trace, not below.
738 hp.setX(hp.x() - (text_size.width() / 2) - padding);
6e6881e2 739
8dbbc7f0
JH
740 hp.setY(get_visual_y() - (row_height_ / 2) +
741 (hover_row * row_height_) -
99029fda 742 row_height_ - text_size.height() - padding);
e2f90c50 743
eae6e30a 744 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
9555ca8b
SA
745}
746
613d097c
JH
747void DecodeTrace::create_decoder_form(int index,
748 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
749 QFormLayout *form)
7491a29f 750{
1cc1c8de 751 GlobalSettings settings;
7491a29f
JH
752
753 assert(dec);
754 const srd_decoder *const decoder = dec->decoder();
755 assert(decoder);
756
ff59fa2c
SA
757 const bool decoder_deletable = index > 0;
758
204bae45 759 pv::widgets::DecoderGroupBox *const group =
27e8df22 760 new pv::widgets::DecoderGroupBox(
580b4f25
UH
761 QString::fromUtf8(decoder->name),
762 tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
763 QString::fromUtf8(decoder->desc)),
764 nullptr, decoder_deletable);
dd048a7e 765 group->set_decoder_visible(dec->shown());
613d097c 766
ff59fa2c
SA
767 if (decoder_deletable) {
768 delete_mapper_.setMapping(group, index);
769 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
770 }
613d097c 771
8dbbc7f0 772 show_hide_mapper_.setMapping(group, index);
dd048a7e 773 connect(group, SIGNAL(show_hide_decoder()),
8dbbc7f0 774 &show_hide_mapper_, SLOT(map()));
dd048a7e 775
204bae45
JH
776 QFormLayout *const decoder_form = new QFormLayout;
777 group->add_layout(decoder_form);
7491a29f 778
9f97b357 779 const list<DecodeChannel> channels = decode_signal_->get_channels();
407c9ebe 780
9f97b357
SA
781 // Add the channels
782 for (DecodeChannel ch : channels) {
783 // Ignore channels not part of the decoder we create the form for
784 if (ch.decoder_ != dec)
785 continue;
407c9ebe 786
9f97b357
SA
787 QComboBox *const combo = create_channel_selector(parent, &ch);
788 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
407c9ebe 789
9f97b357
SA
790 channel_id_map_[combo] = ch.id;
791 init_state_map_[combo_init_state] = ch.id;
407c9ebe 792
7491a29f 793 connect(combo, SIGNAL(currentIndexChanged(int)),
6ac6242b 794 this, SLOT(on_channel_selected(int)));
9f97b357
SA
795 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
796 this, SLOT(on_init_state_changed(int)));
407c9ebe
UH
797
798 QHBoxLayout *const hlayout = new QHBoxLayout;
799 hlayout->addWidget(combo);
9f97b357 800 hlayout->addWidget(combo_init_state);
407c9ebe 801
1cc1c8de 802 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
9f97b357 803 combo_init_state->hide();
7491a29f 804
9f97b357
SA
805 const QString required_flag = ch.is_optional ? QString() : QString("*");
806 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
807 .arg(ch.name, ch.desc, required_flag), hlayout);
7491a29f
JH
808 }
809
6f925ba9 810 shared_ptr<pv::data::DecoderStack> decoder_stack = base_->decoder_stack();
bb7dd726 811
7491a29f 812 // Add the options
3cc9ad7b 813 shared_ptr<binding::Decoder> binding(
bb7dd726 814 new binding::Decoder(decoder_stack, dec));
204bae45 815 binding->add_properties_to_form(decoder_form, true);
7491a29f 816
8dbbc7f0 817 bindings_.push_back(binding);
204bae45
JH
818
819 form->addRow(group);
8dbbc7f0 820 decoder_forms_.push_back(group);
7491a29f
JH
821}
822
9f97b357 823QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
4e5a4405 824{
47e9e7bb 825 const auto sigs(session_.signalbases());
78b0af3e 826
9f97b357 827 // Sort signals in natural order
47e9e7bb 828 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
6f925ba9 829 sort(sig_list.begin(), sig_list.end(),
47e9e7bb
SA
830 [](const shared_ptr<data::SignalBase> &a,
831 const shared_ptr<data::SignalBase> &b) {
832 return strnatcasecmp(a->name().toStdString(),
833 b->name().toStdString()) < 0; });
4e5a4405 834
4e5a4405
JH
835 QComboBox *selector = new QComboBox(parent);
836
4c60462b 837 selector->addItem("-", qVariantFromValue((void*)nullptr));
4e5a4405 838
9f97b357 839 if (!ch->assigned_signal)
4e5a4405
JH
840 selector->setCurrentIndex(0);
841
47e9e7bb
SA
842 for (const shared_ptr<data::SignalBase> &b : sig_list) {
843 assert(b);
79c4a9c8 844 if (b->logic_data() && b->enabled()) {
47e9e7bb
SA
845 selector->addItem(b->name(),
846 qVariantFromValue((void*)b.get()));
5da5d081 847
9f97b357
SA
848 if (ch->assigned_signal == b.get())
849 selector->setCurrentIndex(selector->count() - 1);
4e5a4405
JH
850 }
851 }
852
853 return selector;
854}
855
9f97b357
SA
856QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
857 const DecodeChannel *ch)
407c9ebe
UH
858{
859 QComboBox *selector = new QComboBox(parent);
860
861 selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
862 selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
7df44935 863 selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
407c9ebe 864
9f97b357 865 selector->setCurrentIndex(ch->initial_pin_state);
407c9ebe
UH
866
867 selector->setToolTip("Initial (assumed) pin value before the first sample");
868
869 return selector;
870}
871
ad908057 872void DecodeTrace::on_new_annotations()
9cef9567 873{
8dbbc7f0 874 if (owner_)
6e2c3c85 875 owner_->row_item_appearance_changed(false, true);
9cef9567
JH
876}
877
b9329558 878void DecodeTrace::delete_pressed()
5ed1adf5
JH
879{
880 on_delete();
881}
882
b9329558 883void DecodeTrace::on_delete()
c51482b3 884{
ad908057 885 session_.remove_decode_signal(decode_signal_);
c51482b3
JH
886}
887
6ac6242b 888void DecodeTrace::on_channel_selected(int)
4e5a4405 889{
9f97b357
SA
890 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
891
892 // Determine signal that was selected
893 const data::SignalBase *signal =
894 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
895
896 // Determine decode channel ID this combo box is the channel selector for
897 const uint16_t id = channel_id_map_.at(cb);
898
899 decode_signal_->assign_signal(id, signal);
900}
901
902void DecodeTrace::on_channels_updated()
903{
904 if (owner_)
905 owner_->row_item_appearance_changed(false, true);
4e5a4405
JH
906}
907
9f97b357 908void DecodeTrace::on_init_state_changed(int)
407c9ebe 909{
9f97b357
SA
910 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
911
912 // Determine inital pin state that was selected
913 int init_state = cb->itemData(cb->currentIndex()).value<int>();
914
915 // Determine decode channel ID this combo box is the channel selector for
916 const uint16_t id = init_state_map_.at(cb);
917
918 decode_signal_->set_initial_pin_state(id, init_state);
407c9ebe
UH
919}
920
7491a29f
JH
921void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
922{
ad908057 923 decode_signal_->stack_decoder(decoder);
37fd11b1
JH
924
925 create_popup_form();
7491a29f
JH
926}
927
613d097c
JH
928void DecodeTrace::on_delete_decoder(int index)
929{
ad908057 930 decode_signal_->remove_decoder(index);
613d097c
JH
931
932 // Update the popup
c063290a 933 create_popup_form();
613d097c
JH
934}
935
dd048a7e
JH
936void DecodeTrace::on_show_hide_decoder(int index)
937{
ad908057 938 const bool state = decode_signal_->toggle_decoder_visibility(index);
dd048a7e 939
8dbbc7f0 940 assert(index < (int)decoder_forms_.size());
ad908057 941 decoder_forms_[index]->set_decoder_visible(state);
dd048a7e 942
8dbbc7f0 943 if (owner_)
6e2c3c85 944 owner_->row_item_appearance_changed(false, true);
dd048a7e
JH
945}
946
1573bf16 947} // namespace trace
f4e57597 948} // namespace views
55d3603d 949} // namespace pv