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