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