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