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