]> sigrok.org Git - pulseview.git/blame - pv/views/trace/decodetrace.cpp
Fix #981 by adding non-device signals upon view creation
[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;
7f8517f6 57using std::make_pair;
819f4c25
JH
58using std::max;
59using std::map;
60using std::min;
6f925ba9 61using std::out_of_range;
7f8517f6 62using std::pair;
f9abf97e 63using std::shared_ptr;
067bb624 64using std::make_shared;
53e35b2d 65using std::tie;
78b0af3e 66using std::unordered_set;
819f4c25 67using std::vector;
55d3603d 68
ecd07c20
SA
69using pv::data::decode::Annotation;
70using pv::data::decode::Row;
9f97b357
SA
71using pv::data::DecodeChannel;
72using pv::data::DecodeSignal;
ecd07c20 73
55d3603d 74namespace pv {
f4e57597 75namespace views {
1573bf16 76namespace trace {
55d3603d 77
b9329558 78const QColor DecodeTrace::DecodeColours[4] = {
06bb4e6a
JH
79 QColor(0xEF, 0x29, 0x29), // Red
80 QColor(0xFC, 0xE9, 0x4F), // Yellow
81 QColor(0x8A, 0xE2, 0x34), // Green
82 QColor(0x72, 0x9F, 0xCF) // Blue
83};
84
b9329558 85const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
5dfeb70f 86const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
ad50ac1a 87
88908838 88const int DecodeTrace::ArrowSize = 4;
06e810f2 89const double DecodeTrace::EndCapWidth = 5;
aee9dcf3 90const int DecodeTrace::RowTitleMargin = 10;
06e810f2
JH
91const int DecodeTrace::DrawPadding = 100;
92
5b6ae103
SA
93const int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz
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)));
5b6ae103
SA
159
160 connect(&delayed_trace_updater_, SIGNAL(timeout()),
161 this, SLOT(on_delayed_trace_update()));
162 delayed_trace_updater_.setSingleShot(true);
163 delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
55d3603d
JH
164}
165
b9329558 166bool DecodeTrace::enabled() const
55d3603d
JH
167{
168 return true;
169}
170
6f925ba9 171shared_ptr<data::SignalBase> DecodeTrace::base() const
b6b267bb 172{
bb7dd726 173 return base_;
b6b267bb
JH
174}
175
a5d93c27
JH
176pair<int, int> DecodeTrace::v_extents() const
177{
5b5fa4da 178 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
796e1360 179
e40a79cb
SA
180 // Make an empty decode trace appear symmetrical
181 const int row_count = max(1, max_visible_rows_);
182
183 return make_pair(-row_height, row_height * row_count);
a5d93c27
JH
184}
185
60938e04 186void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
fe08b6e8 187{
3eb29afd 188 Trace::paint_back(p, pp);
97904bf7 189 paint_axis(p, pp, get_visual_y());
fe08b6e8
JH
190}
191
60938e04 192void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
55d3603d 193{
0ce3d18c
JH
194 const int text_height = ViewItemPaintParams::text_height();
195 row_height_ = (text_height * 6) / 4;
196 const int annotation_height = (text_height * 5) / 4;
5dfeb70f 197
ecd07c20 198 const QString err = decode_signal_->error_message();
2ad82c2e 199 if (!err.isEmpty()) {
3eb29afd
JH
200 draw_unresolved_period(
201 p, annotation_height, pp.left(), pp.right());
202 draw_error(p, err, pp);
5dfeb70f
JH
203 return;
204 }
205
cd0c558b
SA
206 // Set default pen to allow for text width calculation
207 p.setPen(Qt::black);
208
f9101a91 209 // Iterate through the rows
be9e7b4b 210 int y = get_visual_y();
3eb29afd
JH
211 pair<uint64_t, uint64_t> sample_range = get_sample_range(
212 pp.left(), pp.right());
5dfeb70f 213
ecd07c20 214 const vector<Row> rows = decode_signal_->visible_rows();
7f8517f6 215
8dbbc7f0 216 visible_rows_.clear();
4fb5fb99 217 for (const Row& row : rows) {
aee9dcf3
SA
218 // Cache the row title widths
219 int row_title_width;
220 try {
221 row_title_width = row_title_widths_.at(row);
6f925ba9 222 } catch (out_of_range) {
aee9dcf3
SA
223 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
224 RowTitleMargin;
225 row_title_widths_[row] = w;
226 row_title_width = w;
227 }
228
229 // Determine the row's color
287d607f
JH
230 size_t base_colour = 0x13579BDF;
231 boost::hash_combine(base_colour, this);
232 boost::hash_combine(base_colour, row.decoder());
233 boost::hash_combine(base_colour, row.row());
234 base_colour >>= 16;
235
f9101a91 236 vector<Annotation> annotations;
ecd07c20 237 decode_signal_->get_annotation_subset(annotations, row,
7f8517f6 238 sample_range.first, sample_range.second);
f9101a91 239 if (!annotations.empty()) {
50631798 240 draw_annotations(annotations, p, annotation_height, pp, y,
aee9dcf3 241 base_colour, row_title_width);
50631798 242
8dbbc7f0 243 y += row_height_;
88908838 244
4fb5fb99 245 visible_rows_.push_back(row);
f9101a91 246 }
7e674e43 247 }
5dfeb70f 248
f9101a91 249 // Draw the hatching
3eb29afd 250 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
eee89ff8 251
a303c2d8
SA
252 if ((int)visible_rows_.size() > max_visible_rows_)
253 owner_->extents_changed(false, true);
254
eee89ff8 255 // Update the maximum row count if needed
6f925ba9 256 max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
55d3603d
JH
257}
258
60938e04 259void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
88908838 260{
8dbbc7f0 261 assert(row_height_);
88908838 262
2ad82c2e 263 for (size_t i = 0; i < visible_rows_.size(); i++) {
8dbbc7f0 264 const int y = i * row_height_ + get_visual_y();
88908838
JH
265
266 p.setPen(QPen(Qt::NoPen));
267 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
268
2ad82c2e 269 if (i != 0) {
88908838 270 const QPointF points[] = {
3eb29afd
JH
271 QPointF(pp.left(), y - ArrowSize),
272 QPointF(pp.left() + ArrowSize, y),
273 QPointF(pp.left(), y + ArrowSize)
88908838
JH
274 };
275 p.drawPolygon(points, countof(points));
276 }
277
3eb29afd
JH
278 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
279 pp.right() - pp.left(), row_height_);
8dbbc7f0 280 const QString h(visible_rows_[i].title());
88908838
JH
281 const int f = Qt::AlignLeft | Qt::AlignVCenter |
282 Qt::TextDontClip;
283
284 // Draw the outline
285 p.setPen(QApplication::palette().color(QPalette::Base));
286 for (int dx = -1; dx <= 1; dx++)
287 for (int dy = -1; dy <= 1; dy++)
288 if (dx != 0 && dy != 0)
289 p.drawText(r.translated(dx, dy), f, h);
290
291 // Draw the text
292 p.setPen(QApplication::palette().color(QPalette::WindowText));
293 p.drawText(r, f, h);
294 }
295}
296
b9329558 297void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
4e5a4405 298{
613d097c
JH
299 using pv::data::decode::Decoder;
300
4e5a4405 301 assert(form);
4e5a4405 302
7491a29f 303 // Add the standard options
4e5a4405
JH
304 Trace::populate_popup_form(parent, form);
305
7491a29f 306 // Add the decoder options
8dbbc7f0 307 bindings_.clear();
9f97b357
SA
308 channel_id_map_.clear();
309 init_state_map_.clear();
8dbbc7f0 310 decoder_forms_.clear();
4e5a4405 311
47747218 312 const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_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 338 stack_button->setMenu(decoder_menu);
b14a9371 339 stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
ce94e4fd
JH
340
341 QHBoxLayout *stack_button_box = new QHBoxLayout;
342 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
343 form->addRow(stack_button_box);
4e5a4405
JH
344}
345
b9329558 346QMenu* DecodeTrace::create_context_menu(QWidget *parent)
c51482b3
JH
347{
348 QMenu *const menu = Trace::create_context_menu(parent);
349
350 menu->addSeparator();
351
352 QAction *const del = new QAction(tr("Delete"), this);
a2d21018 353 del->setShortcuts(QKeySequence::Delete);
c51482b3
JH
354 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
355 menu->addAction(del);
356
357 return menu;
358}
359
50631798
SA
360void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
361 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
aee9dcf3 362 size_t base_colour, int row_title_width)
50631798
SA
363{
364 using namespace pv::data::decode;
365
366 vector<Annotation> a_block;
bdc2a99b 367 int p_end = INT_MIN;
50631798
SA
368
369 double samples_per_pixel, pixels_offset;
370 tie(pixels_offset, samples_per_pixel) =
371 get_pixels_offset_samples_per_pixel();
372
bdc2a99b
SA
373 // Sort the annotations by start sample so that decoders
374 // can't confuse us by creating annotations out of order
375 stable_sort(annotations.begin(), annotations.end(),
376 [](const Annotation &a, const Annotation &b) {
377 return a.start_sample() < b.start_sample(); });
378
50631798
SA
379 // Gather all annotations that form a visual "block" and draw them as such
380 for (const Annotation &a : annotations) {
381
bdc2a99b
SA
382 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
383 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
384 const int a_width = a_end - a_start;
385
386 const int delta = a_end - p_end;
387
388 bool a_is_separate = false;
389
390 // Annotation wider than the threshold for a useful label width?
752281db 391 if (a_width >= min_useful_label_width_) {
bdc2a99b
SA
392 for (const QString &ann_text : a.annotations()) {
393 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
394 // Annotation wide enough to fit a label? Don't put it in a block then
395 if (w <= a_width) {
396 a_is_separate = true;
397 break;
398 }
399 }
400 }
50631798 401
bdc2a99b
SA
402 // Were the previous and this annotation more than a pixel apart?
403 if ((abs(delta) > 1) || a_is_separate) {
404 // Block was broken, draw annotations that form the current block
405 if (a_block.size() == 1) {
aee9dcf3
SA
406 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
407 row_title_width);
bdc2a99b 408 }
50631798 409 else
bdc2a99b 410 draw_annotation_block(a_block, p, h, y, base_colour);
50631798
SA
411
412 a_block.clear();
413 }
414
bdc2a99b 415 if (a_is_separate) {
aee9dcf3 416 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
bdc2a99b
SA
417 // Next annotation must start a new block. delta will be > 1
418 // because we set p_end to INT_MIN but that's okay since
419 // a_block will be empty, so nothing will be drawn
420 p_end = INT_MIN;
421 } else {
422 a_block.push_back(a);
423 p_end = a_end;
424 }
50631798
SA
425 }
426
427 if (a_block.size() == 1)
aee9dcf3
SA
428 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
429 row_title_width);
50631798 430 else
33707990 431 draw_annotation_block(a_block, p, h, y, base_colour);
50631798
SA
432}
433
287d607f 434void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
5b5fa4da 435 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
aee9dcf3 436 size_t base_colour, int row_title_width) const
06e810f2 437{
53e35b2d
JH
438 double samples_per_pixel, pixels_offset;
439 tie(pixels_offset, samples_per_pixel) =
440 get_pixels_offset_samples_per_pixel();
7f8517f6 441
06e810f2
JH
442 const double start = a.start_sample() / samples_per_pixel -
443 pixels_offset;
c063290a 444 const double end = a.end_sample() / samples_per_pixel - 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
c063290a 456 draw_range(a, p, h, start, end, y, pp, row_title_width);
06e810f2
JH
457}
458
33707990
SA
459void DecodeTrace::draw_annotation_block(
460 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
461 int y, size_t base_colour) const
50631798 462{
33707990
SA
463 using namespace pv::data::decode;
464
bdc2a99b
SA
465 if (annotations.empty())
466 return;
467
50631798
SA
468 double samples_per_pixel, pixels_offset;
469 tie(pixels_offset, samples_per_pixel) =
470 get_pixels_offset_samples_per_pixel();
471
33707990
SA
472 const double start = annotations.front().start_sample() /
473 samples_per_pixel - pixels_offset;
474 const double end = annotations.back().end_sample() /
475 samples_per_pixel - pixels_offset;
476
477 const double top = y + .5 - h / 2;
478 const double bottom = y + .5 + h / 2;
33707990
SA
479
480 const size_t colour = (base_colour + annotations.front().format()) %
481 countof(Colours);
482
483 // Check if all annotations are of the same type (i.e. we can use one color)
484 // or if we should use a neutral color (i.e. gray)
8c0302f5 485 const int format = annotations.front().format();
6f925ba9 486 const bool single_format = all_of(
8c0302f5
JH
487 annotations.begin(), annotations.end(),
488 [&](const Annotation &a) { return a.format() == format; });
50631798 489
3082ee93
JH
490 const QRectF rect(start, top, end - start, bottom - top);
491 const int r = h / 4;
492
493 p.setPen(QPen(Qt::NoPen));
494 p.setBrush(Qt::white);
495 p.drawRoundedRect(rect, r, r);
496
33707990
SA
497 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
498 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
499 Qt::Dense4Pattern));
3082ee93 500 p.drawRoundedRect(rect, r, r);
50631798
SA
501}
502
06e810f2 503void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
f765c3db 504 int h, double x, int y) const
06e810f2
JH
505{
506 const QString text = a.annotations().empty() ?
507 QString() : a.annotations().back();
ea86bc4d 508 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
06e810f2
JH
509 0.0) + h;
510 const QRectF rect(x - w / 2, y - h / 2, w, h);
511
06e810f2
JH
512 p.drawRoundedRect(rect, h / 2, h / 2);
513
2a56e448 514 p.setPen(Qt::black);
06e810f2
JH
515 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
516}
517
518void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
f765c3db
SA
519 int h, double start, double end, int y, const ViewItemPaintParams &pp,
520 int row_title_width) const
06e810f2
JH
521{
522 const double top = y + .5 - h / 2;
523 const double bottom = y + .5 + h / 2;
524 const vector<QString> annotations = a.annotations();
525
06e810f2 526 // If the two ends are within 1 pixel, draw a vertical line
2ad82c2e 527 if (start + 1.0 > end) {
06e810f2
JH
528 p.drawLine(QPointF(start, top), QPointF(start, bottom));
529 return;
530 }
531
532 const double cap_width = min((end - start) / 4, EndCapWidth);
533
534 QPointF pts[] = {
535 QPointF(start, y + .5f),
536 QPointF(start + cap_width, top),
537 QPointF(end - cap_width, top),
538 QPointF(end, y + .5f),
539 QPointF(end - cap_width, bottom),
540 QPointF(start + cap_width, bottom)
541 };
542
543 p.drawConvexPolygon(pts, countof(pts));
544
545 if (annotations.empty())
546 return;
547
7352be72
SA
548 const int ann_start = start + cap_width;
549 const int ann_end = end - cap_width;
550
6f925ba9
UH
551 const int real_start = max(ann_start, pp.left() + row_title_width);
552 const int real_end = min(ann_end, pp.right());
7352be72
SA
553 const int real_width = real_end - real_start;
554
555 QRectF rect(real_start, y - h / 2, real_width, h);
0f290e9b
JH
556 if (rect.width() <= 4)
557 return;
558
2a56e448 559 p.setPen(Qt::black);
06e810f2
JH
560
561 // Try to find an annotation that will fit
562 QString best_annotation;
563 int best_width = 0;
564
d9aecf1f 565 for (const QString &a : annotations) {
06e810f2
JH
566 const int w = p.boundingRect(QRectF(), 0, a).width();
567 if (w <= rect.width() && w > best_width)
568 best_annotation = a, best_width = w;
569 }
570
571 if (best_annotation.isEmpty())
572 best_annotation = annotations.back();
573
574 // If not ellide the last in the list
575 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
576 best_annotation, Qt::ElideRight, rect.width()));
577}
578
b9329558 579void DecodeTrace::draw_error(QPainter &p, const QString &message,
5b5fa4da 580 const ViewItemPaintParams &pp)
ad50ac1a 581{
be9e7b4b 582 const int y = get_visual_y();
ad50ac1a
JH
583
584 p.setPen(ErrorBgColour.darker());
585 p.setBrush(ErrorBgColour);
586
587 const QRectF bounding_rect =
a998be27 588 QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
ad50ac1a
JH
589 const QRectF text_rect = p.boundingRect(bounding_rect,
590 Qt::AlignCenter, message);
591 const float r = text_rect.height() / 4;
592
593 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
594 Qt::AbsoluteSize);
595
2a56e448 596 p.setPen(Qt::black);
ad50ac1a
JH
597 p.drawText(text_rect, message);
598}
599
ff83d980 600void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const
5dfeb70f
JH
601{
602 using namespace pv::data;
603 using pv::data::decode::Decoder;
604
53e35b2d
JH
605 double samples_per_pixel, pixels_offset;
606
0c5fe73e 607 const int64_t sample_count = decode_signal_->get_working_sample_count();
5dfeb70f
JH
608 if (sample_count == 0)
609 return;
610
0c5fe73e 611 const int64_t samples_decoded = decode_signal_->get_decoded_sample_count();
5dfeb70f
JH
612 if (sample_count == samples_decoded)
613 return;
614
be9e7b4b 615 const int y = get_visual_y();
7f8517f6 616
ff83d980 617 tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
7f8517f6 618
5dfeb70f
JH
619 const double start = max(samples_decoded /
620 samples_per_pixel - pixels_offset, left - 1.0);
621 const double end = min(sample_count / samples_per_pixel -
622 pixels_offset, right + 1.0);
c063290a 623 const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
5dfeb70f
JH
624
625 p.setPen(QPen(Qt::NoPen));
626 p.setBrush(Qt::white);
627 p.drawRect(no_decode_rect);
628
629 p.setPen(NoDecodeColour);
630 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
631 p.drawRect(no_decode_rect);
632}
633
53e35b2d 634pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
7f8517f6 635{
8dbbc7f0 636 assert(owner_);
7f8517f6 637
8dbbc7f0 638 const View *view = owner_->view();
eae6e30a
JH
639 assert(view);
640
641 const double scale = view->scale();
7f8517f6
SA
642 assert(scale > 0);
643
53e35b2d 644 const double pixels_offset =
ff83d980 645 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
7f8517f6 646
ff83d980 647 double samplerate = decode_signal_->samplerate();
7f8517f6
SA
648
649 // Show sample rate as 1Hz when it is unknown
650 if (samplerate == 0.0)
651 samplerate = 1.0;
652
53e35b2d 653 return make_pair(pixels_offset, samplerate * scale);
7f8517f6
SA
654}
655
db1bf6bf
JH
656pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
657 int x_start, int x_end) const
7f8517f6 658{
53e35b2d
JH
659 double samples_per_pixel, pixels_offset;
660 tie(pixels_offset, samples_per_pixel) =
661 get_pixels_offset_samples_per_pixel();
7f8517f6 662
db1bf6bf
JH
663 const uint64_t start = (uint64_t)max(
664 (x_start + pixels_offset) * samples_per_pixel, 0.0);
665 const uint64_t end = (uint64_t)max(
666 (x_end + pixels_offset) * samples_per_pixel, 0.0);
7f8517f6
SA
667
668 return make_pair(start, end);
669}
670
117cdea3 671int DecodeTrace::get_row_at_point(const QPoint &point)
e2f90c50 672{
8dbbc7f0 673 if (!row_height_)
117cdea3 674 return -1;
e2f90c50 675
99029fda
SA
676 const int y = (point.y() - get_visual_y() + row_height_ / 2);
677
678 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
679 if (y < 0)
680 return -1;
681
682 const int row = y / row_height_;
683
684 if (row >= (int)visible_rows_.size())
117cdea3 685 return -1;
e2f90c50 686
117cdea3 687 return row;
e2f90c50
SA
688}
689
117cdea3 690const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
e2f90c50
SA
691{
692 using namespace pv::data::decode;
693
117cdea3
JH
694 if (!enabled())
695 return QString();
e2f90c50 696
117cdea3
JH
697 const pair<uint64_t, uint64_t> sample_range =
698 get_sample_range(point.x(), point.x() + 1);
699 const int row = get_row_at_point(point);
700 if (row < 0)
701 return QString();
e2f90c50
SA
702
703 vector<pv::data::decode::Annotation> annotations;
704
ff83d980 705 decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
e2f90c50
SA
706 sample_range.first, sample_range.second);
707
708 return (annotations.empty()) ?
709 QString() : annotations[0].annotations().front();
710}
711
117cdea3
JH
712void DecodeTrace::hover_point_changed()
713{
8dbbc7f0 714 assert(owner_);
eae6e30a 715
8dbbc7f0 716 const View *const view = owner_->view();
eae6e30a
JH
717 assert(view);
718
719 QPoint hp = view->hover_point();
117cdea3 720 QString ann = get_annotation_at_point(hp);
e2f90c50 721
eae6e30a 722 assert(view);
e2f90c50 723
8b9df0ad 724 if (!row_height_ || ann.isEmpty()) {
ebdfa094 725 QToolTip::hideText();
117cdea3
JH
726 return;
727 }
6e6881e2 728
117cdea3 729 const int hover_row = get_row_at_point(hp);
6e6881e2 730
117cdea3
JH
731 QFontMetrics m(QToolTip::font());
732 const QRect text_size = m.boundingRect(QRect(), 0, ann);
e2f90c50 733
117cdea3
JH
734 // This is OS-specific and unfortunately we can't query it, so
735 // use an approximation to at least try to minimize the error.
736 const int padding = 8;
6e6881e2 737
117cdea3
JH
738 // Make sure the tool tip doesn't overlap with the mouse cursor.
739 // If it did, the tool tip would constantly hide and re-appear.
740 // We also push it up by one row so that it appears above the
741 // decode trace, not below.
742 hp.setX(hp.x() - (text_size.width() / 2) - padding);
6e6881e2 743
8dbbc7f0
JH
744 hp.setY(get_visual_y() - (row_height_ / 2) +
745 (hover_row * row_height_) -
99029fda 746 row_height_ - text_size.height() - padding);
e2f90c50 747
eae6e30a 748 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
9555ca8b
SA
749}
750
613d097c
JH
751void DecodeTrace::create_decoder_form(int index,
752 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
753 QFormLayout *form)
7491a29f 754{
1cc1c8de 755 GlobalSettings settings;
7491a29f
JH
756
757 assert(dec);
758 const srd_decoder *const decoder = dec->decoder();
759 assert(decoder);
760
ff59fa2c
SA
761 const bool decoder_deletable = index > 0;
762
204bae45 763 pv::widgets::DecoderGroupBox *const group =
27e8df22 764 new pv::widgets::DecoderGroupBox(
580b4f25
UH
765 QString::fromUtf8(decoder->name),
766 tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
767 QString::fromUtf8(decoder->desc)),
768 nullptr, decoder_deletable);
dd048a7e 769 group->set_decoder_visible(dec->shown());
613d097c 770
ff59fa2c
SA
771 if (decoder_deletable) {
772 delete_mapper_.setMapping(group, index);
773 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
774 }
613d097c 775
8dbbc7f0 776 show_hide_mapper_.setMapping(group, index);
dd048a7e 777 connect(group, SIGNAL(show_hide_decoder()),
8dbbc7f0 778 &show_hide_mapper_, SLOT(map()));
dd048a7e 779
204bae45
JH
780 QFormLayout *const decoder_form = new QFormLayout;
781 group->add_layout(decoder_form);
7491a29f 782
47747218 783 const vector<DecodeChannel> channels = decode_signal_->get_channels();
407c9ebe 784
9f97b357
SA
785 // Add the channels
786 for (DecodeChannel ch : channels) {
787 // Ignore channels not part of the decoder we create the form for
788 if (ch.decoder_ != dec)
789 continue;
407c9ebe 790
9f97b357
SA
791 QComboBox *const combo = create_channel_selector(parent, &ch);
792 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
407c9ebe 793
9f97b357
SA
794 channel_id_map_[combo] = ch.id;
795 init_state_map_[combo_init_state] = ch.id;
407c9ebe 796
7491a29f 797 connect(combo, SIGNAL(currentIndexChanged(int)),
6ac6242b 798 this, SLOT(on_channel_selected(int)));
9f97b357
SA
799 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
800 this, SLOT(on_init_state_changed(int)));
407c9ebe
UH
801
802 QHBoxLayout *const hlayout = new QHBoxLayout;
803 hlayout->addWidget(combo);
9f97b357 804 hlayout->addWidget(combo_init_state);
407c9ebe 805
1cc1c8de 806 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
9f97b357 807 combo_init_state->hide();
7491a29f 808
9f97b357
SA
809 const QString required_flag = ch.is_optional ? QString() : QString("*");
810 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
811 .arg(ch.name, ch.desc, required_flag), hlayout);
7491a29f
JH
812 }
813
814 // Add the options
3cc9ad7b 815 shared_ptr<binding::Decoder> binding(
946b52e1 816 new binding::Decoder(decode_signal_, dec));
204bae45 817 binding->add_properties_to_form(decoder_form, true);
7491a29f 818
8dbbc7f0 819 bindings_.push_back(binding);
204bae45
JH
820
821 form->addRow(group);
8dbbc7f0 822 decoder_forms_.push_back(group);
7491a29f
JH
823}
824
9f97b357 825QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
4e5a4405 826{
47e9e7bb 827 const auto sigs(session_.signalbases());
78b0af3e 828
9f97b357 829 // Sort signals in natural order
47e9e7bb 830 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
6f925ba9 831 sort(sig_list.begin(), sig_list.end(),
47e9e7bb
SA
832 [](const shared_ptr<data::SignalBase> &a,
833 const shared_ptr<data::SignalBase> &b) {
834 return strnatcasecmp(a->name().toStdString(),
835 b->name().toStdString()) < 0; });
4e5a4405 836
4e5a4405
JH
837 QComboBox *selector = new QComboBox(parent);
838
4c60462b 839 selector->addItem("-", qVariantFromValue((void*)nullptr));
4e5a4405 840
9f97b357 841 if (!ch->assigned_signal)
4e5a4405
JH
842 selector->setCurrentIndex(0);
843
47e9e7bb
SA
844 for (const shared_ptr<data::SignalBase> &b : sig_list) {
845 assert(b);
79c4a9c8 846 if (b->logic_data() && b->enabled()) {
47e9e7bb
SA
847 selector->addItem(b->name(),
848 qVariantFromValue((void*)b.get()));
5da5d081 849
9f97b357
SA
850 if (ch->assigned_signal == b.get())
851 selector->setCurrentIndex(selector->count() - 1);
4e5a4405
JH
852 }
853 }
854
855 return selector;
856}
857
9f97b357
SA
858QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
859 const DecodeChannel *ch)
407c9ebe
UH
860{
861 QComboBox *selector = new QComboBox(parent);
862
863 selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
864 selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
7df44935 865 selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
407c9ebe 866
9f97b357 867 selector->setCurrentIndex(ch->initial_pin_state);
407c9ebe
UH
868
869 selector->setToolTip("Initial (assumed) pin value before the first sample");
870
871 return selector;
872}
873
ad908057 874void DecodeTrace::on_new_annotations()
5b6ae103
SA
875{
876 if (!delayed_trace_updater_.isActive())
877 delayed_trace_updater_.start();
878}
879
880void DecodeTrace::on_delayed_trace_update()
9cef9567 881{
8dbbc7f0 882 if (owner_)
6e2c3c85 883 owner_->row_item_appearance_changed(false, true);
9cef9567
JH
884}
885
b9329558 886void DecodeTrace::delete_pressed()
5ed1adf5
JH
887{
888 on_delete();
889}
890
b9329558 891void DecodeTrace::on_delete()
c51482b3 892{
ad908057 893 session_.remove_decode_signal(decode_signal_);
c51482b3
JH
894}
895
6ac6242b 896void DecodeTrace::on_channel_selected(int)
4e5a4405 897{
9f97b357
SA
898 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
899
900 // Determine signal that was selected
901 const data::SignalBase *signal =
902 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
903
904 // Determine decode channel ID this combo box is the channel selector for
905 const uint16_t id = channel_id_map_.at(cb);
906
907 decode_signal_->assign_signal(id, signal);
908}
909
910void DecodeTrace::on_channels_updated()
911{
912 if (owner_)
913 owner_->row_item_appearance_changed(false, true);
4e5a4405
JH
914}
915
9f97b357 916void DecodeTrace::on_init_state_changed(int)
407c9ebe 917{
9f97b357
SA
918 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
919
920 // Determine inital pin state that was selected
921 int init_state = cb->itemData(cb->currentIndex()).value<int>();
922
923 // Determine decode channel ID this combo box is the channel selector for
924 const uint16_t id = init_state_map_.at(cb);
925
926 decode_signal_->set_initial_pin_state(id, init_state);
407c9ebe
UH
927}
928
7491a29f
JH
929void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
930{
ad908057 931 decode_signal_->stack_decoder(decoder);
37fd11b1
JH
932
933 create_popup_form();
7491a29f
JH
934}
935
613d097c
JH
936void DecodeTrace::on_delete_decoder(int index)
937{
ad908057 938 decode_signal_->remove_decoder(index);
613d097c
JH
939
940 // Update the popup
c063290a 941 create_popup_form();
613d097c
JH
942}
943
dd048a7e
JH
944void DecodeTrace::on_show_hide_decoder(int index)
945{
ad908057 946 const bool state = decode_signal_->toggle_decoder_visibility(index);
dd048a7e 947
8dbbc7f0 948 assert(index < (int)decoder_forms_.size());
ad908057 949 decoder_forms_[index]->set_decoder_visible(state);
dd048a7e 950
8dbbc7f0 951 if (owner_)
6e2c3c85 952 owner_->row_item_appearance_changed(false, true);
dd048a7e
JH
953}
954
1573bf16 955} // namespace trace
f4e57597 956} // namespace views
55d3603d 957} // namespace pv