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