]> sigrok.org Git - pulseview.git/blame - pv/views/trace/decodetrace.cpp
DecodeTrace: Don't show tooltips destined for the header area
[pulseview.git] / pv / views / trace / decodetrace.cpp
CommitLineData
55d3603d
JH
1/*
2 * This file is part of the PulseView project.
3 *
4 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
efdec55a 17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
55d3603d
JH
18 */
19
20extern "C" {
21#include <libsigrokdecode/libsigrokdecode.h>
22}
23
c3a740dd
JH
24#include <mutex>
25
06bb4e6a
JH
26#include <extdef.h>
27
53e35b2d
JH
28#include <tuple>
29
a855d71e 30#include <boost/functional/hash.hpp>
b213ef09 31
c51482b3 32#include <QAction>
d7c0ca4a 33#include <QApplication>
4e5a4405
JH
34#include <QComboBox>
35#include <QFormLayout>
36#include <QLabel>
b213ef09 37#include <QMenu>
ce94e4fd 38#include <QPushButton>
e2f90c50 39#include <QToolTip>
c51482b3 40
2acdb232 41#include "decodetrace.hpp"
1573bf16
SA
42#include "view.hpp"
43#include "viewport.hpp"
2acdb232 44
1cc1c8de 45#include <pv/globalsettings.hpp>
ad908057
SA
46#include <pv/session.hpp>
47#include <pv/strnatcmp.hpp>
48#include <pv/data/decodesignal.hpp>
aca9aa83 49#include <pv/data/decode/annotation.hpp>
2acdb232
JH
50#include <pv/data/decode/decoder.hpp>
51#include <pv/data/logic.hpp>
f3d66e52 52#include <pv/data/logicsegment.hpp>
2acdb232
JH
53#include <pv/widgets/decodergroupbox.hpp>
54#include <pv/widgets/decodermenu.hpp>
119aff65 55
6f925ba9 56using std::all_of;
7f8517f6 57using std::make_pair;
819f4c25 58using std::max;
819f4c25 59using std::min;
6f925ba9 60using std::out_of_range;
7f8517f6 61using std::pair;
f9abf97e 62using std::shared_ptr;
53e35b2d 63using std::tie;
819f4c25 64using std::vector;
55d3603d 65
ecd07c20
SA
66using pv::data::decode::Annotation;
67using pv::data::decode::Row;
9f97b357
SA
68using pv::data::DecodeChannel;
69using pv::data::DecodeSignal;
ecd07c20 70
55d3603d 71namespace pv {
f4e57597 72namespace views {
1573bf16 73namespace trace {
55d3603d 74
b9329558 75const QColor DecodeTrace::DecodeColours[4] = {
06bb4e6a
JH
76 QColor(0xEF, 0x29, 0x29), // Red
77 QColor(0xFC, 0xE9, 0x4F), // Yellow
78 QColor(0x8A, 0xE2, 0x34), // Green
79 QColor(0x72, 0x9F, 0xCF) // Blue
80};
81
b9329558 82const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
5dfeb70f 83const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
ad50ac1a 84
88908838 85const int DecodeTrace::ArrowSize = 4;
06e810f2 86const double DecodeTrace::EndCapWidth = 5;
aee9dcf3 87const int DecodeTrace::RowTitleMargin = 10;
06e810f2
JH
88const int DecodeTrace::DrawPadding = 100;
89
5b6ae103
SA
90const int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz
91
287d607f
JH
92const QColor DecodeTrace::Colours[16] = {
93 QColor(0xEF, 0x29, 0x29),
94 QColor(0xF6, 0x6A, 0x32),
95 QColor(0xFC, 0xAE, 0x3E),
96 QColor(0xFB, 0xCA, 0x47),
97 QColor(0xFC, 0xE9, 0x4F),
98 QColor(0xCD, 0xF0, 0x40),
99 QColor(0x8A, 0xE2, 0x34),
100 QColor(0x4E, 0xDC, 0x44),
101 QColor(0x55, 0xD7, 0x95),
102 QColor(0x64, 0xD1, 0xD2),
103 QColor(0x72, 0x9F, 0xCF),
104 QColor(0xD4, 0x76, 0xC4),
105 QColor(0x9D, 0x79, 0xB9),
106 QColor(0xAD, 0x7F, 0xA8),
107 QColor(0xC2, 0x62, 0x9B),
108 QColor(0xD7, 0x47, 0x6F)
109};
110
111const QColor DecodeTrace::OutlineColours[16] = {
112 QColor(0x77, 0x14, 0x14),
113 QColor(0x7B, 0x35, 0x19),
114 QColor(0x7E, 0x57, 0x1F),
115 QColor(0x7D, 0x65, 0x23),
116 QColor(0x7E, 0x74, 0x27),
117 QColor(0x66, 0x78, 0x20),
118 QColor(0x45, 0x71, 0x1A),
119 QColor(0x27, 0x6E, 0x22),
120 QColor(0x2A, 0x6B, 0x4A),
121 QColor(0x32, 0x68, 0x69),
122 QColor(0x39, 0x4F, 0x67),
123 QColor(0x6A, 0x3B, 0x62),
124 QColor(0x4E, 0x3C, 0x5C),
125 QColor(0x56, 0x3F, 0x54),
126 QColor(0x61, 0x31, 0x4D),
127 QColor(0x6B, 0x23, 0x37)
06e810f2
JH
128};
129
2b81ae46 130DecodeTrace::DecodeTrace(pv::Session &session,
bb7dd726 131 shared_ptr<data::SignalBase> signalbase, int index) :
bf0edd2b 132 Trace(signalbase),
8dbbc7f0 133 session_(session),
8dbbc7f0 134 row_height_(0),
eee89ff8 135 max_visible_rows_(0),
8dbbc7f0
JH
136 delete_mapper_(this),
137 show_hide_mapper_(this)
55d3603d 138{
ad908057 139 decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
e0fc5810 140
752281db
SA
141 // Determine shortest string we want to see displayed in full
142 QFontMetrics m(QApplication::font());
143 min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
144
455bc29a 145 base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
9cef9567 146
ad908057
SA
147 connect(decode_signal_.get(), SIGNAL(new_annotations()),
148 this, SLOT(on_new_annotations()));
1b56c646
SA
149 connect(decode_signal_.get(), SIGNAL(decode_finished()),
150 this, SLOT(on_decode_finished()));
9f97b357
SA
151 connect(decode_signal_.get(), SIGNAL(channels_updated()),
152 this, SLOT(on_channels_updated()));
153
8dbbc7f0 154 connect(&delete_mapper_, SIGNAL(mapped(int)),
613d097c 155 this, SLOT(on_delete_decoder(int)));
8dbbc7f0 156 connect(&show_hide_mapper_, SIGNAL(mapped(int)),
dd048a7e 157 this, SLOT(on_show_hide_decoder(int)));
5b6ae103
SA
158
159 connect(&delayed_trace_updater_, SIGNAL(timeout()),
160 this, SLOT(on_delayed_trace_update()));
161 delayed_trace_updater_.setSingleShot(true);
162 delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
55d3603d
JH
163}
164
b9329558 165bool DecodeTrace::enabled() const
55d3603d
JH
166{
167 return true;
168}
169
6f925ba9 170shared_ptr<data::SignalBase> DecodeTrace::base() const
b6b267bb 171{
bb7dd726 172 return base_;
b6b267bb
JH
173}
174
a5d93c27
JH
175pair<int, int> DecodeTrace::v_extents() const
176{
5b5fa4da 177 const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
796e1360 178
e40a79cb
SA
179 // Make an empty decode trace appear symmetrical
180 const int row_count = max(1, max_visible_rows_);
181
182 return make_pair(-row_height, row_height * row_count);
a5d93c27
JH
183}
184
60938e04 185void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
fe08b6e8 186{
3eb29afd 187 Trace::paint_back(p, pp);
97904bf7 188 paint_axis(p, pp, get_visual_y());
fe08b6e8
JH
189}
190
60938e04 191void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
55d3603d 192{
0ce3d18c
JH
193 const int text_height = ViewItemPaintParams::text_height();
194 row_height_ = (text_height * 6) / 4;
195 const int annotation_height = (text_height * 5) / 4;
5dfeb70f 196
ecd07c20 197 const QString err = decode_signal_->error_message();
2ad82c2e 198 if (!err.isEmpty()) {
3eb29afd
JH
199 draw_unresolved_period(
200 p, annotation_height, pp.left(), pp.right());
201 draw_error(p, err, pp);
5dfeb70f
JH
202 return;
203 }
204
cd0c558b
SA
205 // Set default pen to allow for text width calculation
206 p.setPen(Qt::black);
207
f9101a91 208 // Iterate through the rows
be9e7b4b 209 int y = get_visual_y();
3eb29afd
JH
210 pair<uint64_t, uint64_t> sample_range = get_sample_range(
211 pp.left(), pp.right());
5dfeb70f 212
ecd07c20 213 const vector<Row> rows = decode_signal_->visible_rows();
7f8517f6 214
8dbbc7f0 215 visible_rows_.clear();
4fb5fb99 216 for (const Row& row : rows) {
aee9dcf3
SA
217 // Cache the row title widths
218 int row_title_width;
219 try {
220 row_title_width = row_title_widths_.at(row);
6f925ba9 221 } catch (out_of_range) {
aee9dcf3
SA
222 const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
223 RowTitleMargin;
224 row_title_widths_[row] = w;
225 row_title_width = w;
226 }
227
228 // Determine the row's color
287d607f
JH
229 size_t base_colour = 0x13579BDF;
230 boost::hash_combine(base_colour, this);
231 boost::hash_combine(base_colour, row.decoder());
232 boost::hash_combine(base_colour, row.row());
233 base_colour >>= 16;
234
f9101a91 235 vector<Annotation> annotations;
ecd07c20 236 decode_signal_->get_annotation_subset(annotations, row,
7f8517f6 237 sample_range.first, sample_range.second);
f9101a91 238 if (!annotations.empty()) {
50631798 239 draw_annotations(annotations, p, annotation_height, pp, y,
aee9dcf3 240 base_colour, row_title_width);
50631798 241
8dbbc7f0 242 y += row_height_;
88908838 243
4fb5fb99 244 visible_rows_.push_back(row);
f9101a91 245 }
7e674e43 246 }
5dfeb70f 247
f9101a91 248 // Draw the hatching
3eb29afd 249 draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
eee89ff8 250
a303c2d8
SA
251 if ((int)visible_rows_.size() > max_visible_rows_)
252 owner_->extents_changed(false, true);
253
eee89ff8 254 // Update the maximum row count if needed
6f925ba9 255 max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
55d3603d
JH
256}
257
60938e04 258void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
88908838 259{
8dbbc7f0 260 assert(row_height_);
88908838 261
2ad82c2e 262 for (size_t i = 0; i < visible_rows_.size(); i++) {
8dbbc7f0 263 const int y = i * row_height_ + get_visual_y();
88908838
JH
264
265 p.setPen(QPen(Qt::NoPen));
266 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
267
2ad82c2e 268 if (i != 0) {
88908838 269 const QPointF points[] = {
3eb29afd
JH
270 QPointF(pp.left(), y - ArrowSize),
271 QPointF(pp.left() + ArrowSize, y),
272 QPointF(pp.left(), y + ArrowSize)
88908838
JH
273 };
274 p.drawPolygon(points, countof(points));
275 }
276
3eb29afd
JH
277 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
278 pp.right() - pp.left(), row_height_);
8dbbc7f0 279 const QString h(visible_rows_[i].title());
88908838
JH
280 const int f = Qt::AlignLeft | Qt::AlignVCenter |
281 Qt::TextDontClip;
282
283 // Draw the outline
284 p.setPen(QApplication::palette().color(QPalette::Base));
285 for (int dx = -1; dx <= 1; dx++)
286 for (int dy = -1; dy <= 1; dy++)
287 if (dx != 0 && dy != 0)
288 p.drawText(r.translated(dx, dy), f, h);
289
290 // Draw the text
291 p.setPen(QApplication::palette().color(QPalette::WindowText));
292 p.drawText(r, f, h);
293 }
294}
295
b9329558 296void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
4e5a4405 297{
613d097c
JH
298 using pv::data::decode::Decoder;
299
4e5a4405 300 assert(form);
4e5a4405 301
7491a29f 302 // Add the standard options
4e5a4405
JH
303 Trace::populate_popup_form(parent, form);
304
7491a29f 305 // Add the decoder options
8dbbc7f0 306 bindings_.clear();
9f97b357
SA
307 channel_id_map_.clear();
308 init_state_map_.clear();
8dbbc7f0 309 decoder_forms_.clear();
4e5a4405 310
47747218 311 const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_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 337 stack_button->setMenu(decoder_menu);
b14a9371 338 stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
ce94e4fd
JH
339
340 QHBoxLayout *stack_button_box = new QHBoxLayout;
341 stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
342 form->addRow(stack_button_box);
4e5a4405
JH
343}
344
b9329558 345QMenu* DecodeTrace::create_context_menu(QWidget *parent)
c51482b3
JH
346{
347 QMenu *const menu = Trace::create_context_menu(parent);
348
349 menu->addSeparator();
350
351 QAction *const del = new QAction(tr("Delete"), this);
a2d21018 352 del->setShortcuts(QKeySequence::Delete);
c51482b3
JH
353 connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
354 menu->addAction(del);
355
356 return menu;
357}
358
50631798
SA
359void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
360 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
aee9dcf3 361 size_t base_colour, int row_title_width)
50631798
SA
362{
363 using namespace pv::data::decode;
364
365 vector<Annotation> a_block;
bdc2a99b 366 int p_end = INT_MIN;
50631798
SA
367
368 double samples_per_pixel, pixels_offset;
369 tie(pixels_offset, samples_per_pixel) =
370 get_pixels_offset_samples_per_pixel();
371
bdc2a99b
SA
372 // Sort the annotations by start sample so that decoders
373 // can't confuse us by creating annotations out of order
374 stable_sort(annotations.begin(), annotations.end(),
375 [](const Annotation &a, const Annotation &b) {
376 return a.start_sample() < b.start_sample(); });
377
50631798
SA
378 // Gather all annotations that form a visual "block" and draw them as such
379 for (const Annotation &a : annotations) {
380
bdc2a99b
SA
381 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
382 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
383 const int a_width = a_end - a_start;
384
385 const int delta = a_end - p_end;
386
387 bool a_is_separate = false;
388
389 // Annotation wider than the threshold for a useful label width?
752281db 390 if (a_width >= min_useful_label_width_) {
bdc2a99b
SA
391 for (const QString &ann_text : a.annotations()) {
392 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
393 // Annotation wide enough to fit a label? Don't put it in a block then
394 if (w <= a_width) {
395 a_is_separate = true;
396 break;
397 }
398 }
399 }
50631798 400
bdc2a99b
SA
401 // Were the previous and this annotation more than a pixel apart?
402 if ((abs(delta) > 1) || a_is_separate) {
403 // Block was broken, draw annotations that form the current block
404 if (a_block.size() == 1) {
aee9dcf3
SA
405 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
406 row_title_width);
bdc2a99b 407 }
50631798 408 else
bdc2a99b 409 draw_annotation_block(a_block, p, h, y, base_colour);
50631798
SA
410
411 a_block.clear();
412 }
413
bdc2a99b 414 if (a_is_separate) {
aee9dcf3 415 draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
bdc2a99b
SA
416 // Next annotation must start a new block. delta will be > 1
417 // because we set p_end to INT_MIN but that's okay since
418 // a_block will be empty, so nothing will be drawn
419 p_end = INT_MIN;
420 } else {
421 a_block.push_back(a);
422 p_end = a_end;
423 }
50631798
SA
424 }
425
426 if (a_block.size() == 1)
aee9dcf3
SA
427 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
428 row_title_width);
50631798 429 else
33707990 430 draw_annotation_block(a_block, p, h, y, base_colour);
50631798
SA
431}
432
287d607f 433void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
5b5fa4da 434 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
aee9dcf3 435 size_t base_colour, int row_title_width) const
06e810f2 436{
53e35b2d
JH
437 double samples_per_pixel, pixels_offset;
438 tie(pixels_offset, samples_per_pixel) =
439 get_pixels_offset_samples_per_pixel();
7f8517f6 440
06e810f2
JH
441 const double start = a.start_sample() / samples_per_pixel -
442 pixels_offset;
c063290a 443 const double end = a.end_sample() / samples_per_pixel - 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
c063290a 455 draw_range(a, p, h, start, end, y, pp, row_title_width);
06e810f2
JH
456}
457
33707990
SA
458void DecodeTrace::draw_annotation_block(
459 vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
460 int y, size_t base_colour) const
50631798 461{
33707990
SA
462 using namespace pv::data::decode;
463
bdc2a99b
SA
464 if (annotations.empty())
465 return;
466
50631798
SA
467 double samples_per_pixel, pixels_offset;
468 tie(pixels_offset, samples_per_pixel) =
469 get_pixels_offset_samples_per_pixel();
470
33707990
SA
471 const double start = annotations.front().start_sample() /
472 samples_per_pixel - pixels_offset;
473 const double end = annotations.back().end_sample() /
474 samples_per_pixel - pixels_offset;
475
476 const double top = y + .5 - h / 2;
477 const double bottom = y + .5 + h / 2;
33707990
SA
478
479 const size_t colour = (base_colour + annotations.front().format()) %
480 countof(Colours);
481
482 // Check if all annotations are of the same type (i.e. we can use one color)
483 // or if we should use a neutral color (i.e. gray)
8c0302f5 484 const int format = annotations.front().format();
6f925ba9 485 const bool single_format = all_of(
8c0302f5
JH
486 annotations.begin(), annotations.end(),
487 [&](const Annotation &a) { return a.format() == format; });
50631798 488
3082ee93
JH
489 const QRectF rect(start, top, end - start, bottom - top);
490 const int r = h / 4;
491
492 p.setPen(QPen(Qt::NoPen));
493 p.setBrush(Qt::white);
494 p.drawRoundedRect(rect, r, r);
495
33707990
SA
496 p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
497 p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
498 Qt::Dense4Pattern));
3082ee93 499 p.drawRoundedRect(rect, r, r);
50631798
SA
500}
501
06e810f2 502void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
f765c3db 503 int h, double x, int y) const
06e810f2
JH
504{
505 const QString text = a.annotations().empty() ?
506 QString() : a.annotations().back();
ea86bc4d 507 const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
06e810f2
JH
508 0.0) + h;
509 const QRectF rect(x - w / 2, y - h / 2, w, h);
510
06e810f2
JH
511 p.drawRoundedRect(rect, h / 2, h / 2);
512
2a56e448 513 p.setPen(Qt::black);
06e810f2
JH
514 p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
515}
516
517void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
f765c3db
SA
518 int h, double start, double end, int y, const ViewItemPaintParams &pp,
519 int row_title_width) const
06e810f2
JH
520{
521 const double top = y + .5 - h / 2;
522 const double bottom = y + .5 + h / 2;
523 const vector<QString> annotations = a.annotations();
524
06e810f2 525 // If the two ends are within 1 pixel, draw a vertical line
2ad82c2e 526 if (start + 1.0 > end) {
06e810f2
JH
527 p.drawLine(QPointF(start, top), QPointF(start, bottom));
528 return;
529 }
530
531 const double cap_width = min((end - start) / 4, EndCapWidth);
532
533 QPointF pts[] = {
534 QPointF(start, y + .5f),
535 QPointF(start + cap_width, top),
536 QPointF(end - cap_width, top),
537 QPointF(end, y + .5f),
538 QPointF(end - cap_width, bottom),
539 QPointF(start + cap_width, bottom)
540 };
541
542 p.drawConvexPolygon(pts, countof(pts));
543
544 if (annotations.empty())
545 return;
546
7352be72
SA
547 const int ann_start = start + cap_width;
548 const int ann_end = end - cap_width;
549
6f925ba9
UH
550 const int real_start = max(ann_start, pp.left() + row_title_width);
551 const int real_end = min(ann_end, pp.right());
7352be72
SA
552 const int real_width = real_end - real_start;
553
554 QRectF rect(real_start, y - h / 2, real_width, h);
0f290e9b
JH
555 if (rect.width() <= 4)
556 return;
557
2a56e448 558 p.setPen(Qt::black);
06e810f2
JH
559
560 // Try to find an annotation that will fit
561 QString best_annotation;
562 int best_width = 0;
563
d9aecf1f 564 for (const QString &a : annotations) {
06e810f2
JH
565 const int w = p.boundingRect(QRectF(), 0, a).width();
566 if (w <= rect.width() && w > best_width)
567 best_annotation = a, best_width = w;
568 }
569
570 if (best_annotation.isEmpty())
571 best_annotation = annotations.back();
572
573 // If not ellide the last in the list
574 p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
575 best_annotation, Qt::ElideRight, rect.width()));
576}
577
b9329558 578void DecodeTrace::draw_error(QPainter &p, const QString &message,
5b5fa4da 579 const ViewItemPaintParams &pp)
ad50ac1a 580{
be9e7b4b 581 const int y = get_visual_y();
ad50ac1a
JH
582
583 p.setPen(ErrorBgColour.darker());
584 p.setBrush(ErrorBgColour);
585
586 const QRectF bounding_rect =
a998be27 587 QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
ad50ac1a
JH
588 const QRectF text_rect = p.boundingRect(bounding_rect,
589 Qt::AlignCenter, message);
590 const float r = text_rect.height() / 4;
591
592 p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
593 Qt::AbsoluteSize);
594
2a56e448 595 p.setPen(Qt::black);
ad50ac1a
JH
596 p.drawText(text_rect, message);
597}
598
ff83d980 599void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const
5dfeb70f
JH
600{
601 using namespace pv::data;
602 using pv::data::decode::Decoder;
603
53e35b2d
JH
604 double samples_per_pixel, pixels_offset;
605
0c5fe73e 606 const int64_t sample_count = decode_signal_->get_working_sample_count();
5dfeb70f
JH
607 if (sample_count == 0)
608 return;
609
0c5fe73e 610 const int64_t samples_decoded = decode_signal_->get_decoded_sample_count();
5dfeb70f
JH
611 if (sample_count == samples_decoded)
612 return;
613
be9e7b4b 614 const int y = get_visual_y();
7f8517f6 615
ff83d980 616 tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
7f8517f6 617
5dfeb70f
JH
618 const double start = max(samples_decoded /
619 samples_per_pixel - pixels_offset, left - 1.0);
620 const double end = min(sample_count / samples_per_pixel -
621 pixels_offset, right + 1.0);
c063290a 622 const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
5dfeb70f
JH
623
624 p.setPen(QPen(Qt::NoPen));
625 p.setBrush(Qt::white);
626 p.drawRect(no_decode_rect);
627
628 p.setPen(NoDecodeColour);
629 p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
630 p.drawRect(no_decode_rect);
631}
632
53e35b2d 633pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
7f8517f6 634{
8dbbc7f0 635 assert(owner_);
7f8517f6 636
8dbbc7f0 637 const View *view = owner_->view();
eae6e30a
JH
638 assert(view);
639
640 const double scale = view->scale();
7f8517f6
SA
641 assert(scale > 0);
642
53e35b2d 643 const double pixels_offset =
ff83d980 644 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
7f8517f6 645
ff83d980 646 double samplerate = decode_signal_->samplerate();
7f8517f6
SA
647
648 // Show sample rate as 1Hz when it is unknown
649 if (samplerate == 0.0)
650 samplerate = 1.0;
651
53e35b2d 652 return make_pair(pixels_offset, samplerate * scale);
7f8517f6
SA
653}
654
db1bf6bf
JH
655pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
656 int x_start, int x_end) const
7f8517f6 657{
53e35b2d
JH
658 double samples_per_pixel, pixels_offset;
659 tie(pixels_offset, samples_per_pixel) =
660 get_pixels_offset_samples_per_pixel();
7f8517f6 661
db1bf6bf
JH
662 const uint64_t start = (uint64_t)max(
663 (x_start + pixels_offset) * samples_per_pixel, 0.0);
664 const uint64_t end = (uint64_t)max(
665 (x_end + pixels_offset) * samples_per_pixel, 0.0);
7f8517f6
SA
666
667 return make_pair(start, end);
668}
669
117cdea3 670int DecodeTrace::get_row_at_point(const QPoint &point)
e2f90c50 671{
8dbbc7f0 672 if (!row_height_)
117cdea3 673 return -1;
e2f90c50 674
99029fda
SA
675 const int y = (point.y() - get_visual_y() + row_height_ / 2);
676
677 /* Integer divison of (x-1)/x would yield 0, so we check for this. */
678 if (y < 0)
679 return -1;
680
681 const int row = y / row_height_;
682
683 if (row >= (int)visible_rows_.size())
117cdea3 684 return -1;
e2f90c50 685
117cdea3 686 return row;
e2f90c50
SA
687}
688
117cdea3 689const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
e2f90c50
SA
690{
691 using namespace pv::data::decode;
692
117cdea3
JH
693 if (!enabled())
694 return QString();
e2f90c50 695
117cdea3
JH
696 const pair<uint64_t, uint64_t> sample_range =
697 get_sample_range(point.x(), point.x() + 1);
698 const int row = get_row_at_point(point);
699 if (row < 0)
700 return QString();
e2f90c50
SA
701
702 vector<pv::data::decode::Annotation> annotations;
703
ff83d980 704 decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
e2f90c50
SA
705 sample_range.first, sample_range.second);
706
707 return (annotations.empty()) ?
708 QString() : annotations[0].annotations().front();
709}
710
117cdea3
JH
711void DecodeTrace::hover_point_changed()
712{
8dbbc7f0 713 assert(owner_);
eae6e30a 714
8dbbc7f0 715 const View *const view = owner_->view();
eae6e30a
JH
716 assert(view);
717
718 QPoint hp = view->hover_point();
53b652bd
SA
719
720 if (hp.x() == 0) {
721 QToolTip::hideText();
722 return;
723 }
724
117cdea3 725 QString ann = get_annotation_at_point(hp);
e2f90c50 726
eae6e30a 727 assert(view);
e2f90c50 728
8b9df0ad 729 if (!row_height_ || ann.isEmpty()) {
ebdfa094 730 QToolTip::hideText();
117cdea3
JH
731 return;
732 }
6e6881e2 733
117cdea3 734 const int hover_row = get_row_at_point(hp);
6e6881e2 735
117cdea3
JH
736 QFontMetrics m(QToolTip::font());
737 const QRect text_size = m.boundingRect(QRect(), 0, ann);
e2f90c50 738
117cdea3
JH
739 // This is OS-specific and unfortunately we can't query it, so
740 // use an approximation to at least try to minimize the error.
741 const int padding = 8;
6e6881e2 742
117cdea3
JH
743 // Make sure the tool tip doesn't overlap with the mouse cursor.
744 // If it did, the tool tip would constantly hide and re-appear.
745 // We also push it up by one row so that it appears above the
746 // decode trace, not below.
747 hp.setX(hp.x() - (text_size.width() / 2) - padding);
6e6881e2 748
8dbbc7f0
JH
749 hp.setY(get_visual_y() - (row_height_ / 2) +
750 (hover_row * row_height_) -
99029fda 751 row_height_ - text_size.height() - padding);
e2f90c50 752
eae6e30a 753 QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
9555ca8b
SA
754}
755
613d097c
JH
756void DecodeTrace::create_decoder_form(int index,
757 shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
758 QFormLayout *form)
7491a29f 759{
1cc1c8de 760 GlobalSettings settings;
7491a29f
JH
761
762 assert(dec);
763 const srd_decoder *const decoder = dec->decoder();
764 assert(decoder);
765
ff59fa2c
SA
766 const bool decoder_deletable = index > 0;
767
204bae45 768 pv::widgets::DecoderGroupBox *const group =
27e8df22 769 new pv::widgets::DecoderGroupBox(
580b4f25
UH
770 QString::fromUtf8(decoder->name),
771 tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
772 QString::fromUtf8(decoder->desc)),
773 nullptr, decoder_deletable);
dd048a7e 774 group->set_decoder_visible(dec->shown());
613d097c 775
ff59fa2c
SA
776 if (decoder_deletable) {
777 delete_mapper_.setMapping(group, index);
778 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
779 }
613d097c 780
8dbbc7f0 781 show_hide_mapper_.setMapping(group, index);
dd048a7e 782 connect(group, SIGNAL(show_hide_decoder()),
8dbbc7f0 783 &show_hide_mapper_, SLOT(map()));
dd048a7e 784
204bae45
JH
785 QFormLayout *const decoder_form = new QFormLayout;
786 group->add_layout(decoder_form);
7491a29f 787
47747218 788 const vector<DecodeChannel> channels = decode_signal_->get_channels();
407c9ebe 789
9f97b357
SA
790 // Add the channels
791 for (DecodeChannel ch : channels) {
792 // Ignore channels not part of the decoder we create the form for
793 if (ch.decoder_ != dec)
794 continue;
407c9ebe 795
9f97b357
SA
796 QComboBox *const combo = create_channel_selector(parent, &ch);
797 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
407c9ebe 798
9f97b357
SA
799 channel_id_map_[combo] = ch.id;
800 init_state_map_[combo_init_state] = ch.id;
407c9ebe 801
7491a29f 802 connect(combo, SIGNAL(currentIndexChanged(int)),
6ac6242b 803 this, SLOT(on_channel_selected(int)));
9f97b357
SA
804 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
805 this, SLOT(on_init_state_changed(int)));
407c9ebe
UH
806
807 QHBoxLayout *const hlayout = new QHBoxLayout;
808 hlayout->addWidget(combo);
9f97b357 809 hlayout->addWidget(combo_init_state);
407c9ebe 810
1cc1c8de 811 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
9f97b357 812 combo_init_state->hide();
7491a29f 813
9f97b357
SA
814 const QString required_flag = ch.is_optional ? QString() : QString("*");
815 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
816 .arg(ch.name, ch.desc, required_flag), hlayout);
7491a29f
JH
817 }
818
819 // Add the options
3cc9ad7b 820 shared_ptr<binding::Decoder> binding(
946b52e1 821 new binding::Decoder(decode_signal_, dec));
204bae45 822 binding->add_properties_to_form(decoder_form, true);
7491a29f 823
8dbbc7f0 824 bindings_.push_back(binding);
204bae45
JH
825
826 form->addRow(group);
8dbbc7f0 827 decoder_forms_.push_back(group);
7491a29f
JH
828}
829
9f97b357 830QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
4e5a4405 831{
47e9e7bb 832 const auto sigs(session_.signalbases());
78b0af3e 833
9f97b357 834 // Sort signals in natural order
47e9e7bb 835 vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
6f925ba9 836 sort(sig_list.begin(), sig_list.end(),
47e9e7bb
SA
837 [](const shared_ptr<data::SignalBase> &a,
838 const shared_ptr<data::SignalBase> &b) {
839 return strnatcasecmp(a->name().toStdString(),
840 b->name().toStdString()) < 0; });
4e5a4405 841
4e5a4405
JH
842 QComboBox *selector = new QComboBox(parent);
843
4c60462b 844 selector->addItem("-", qVariantFromValue((void*)nullptr));
4e5a4405 845
9f97b357 846 if (!ch->assigned_signal)
4e5a4405
JH
847 selector->setCurrentIndex(0);
848
47e9e7bb
SA
849 for (const shared_ptr<data::SignalBase> &b : sig_list) {
850 assert(b);
79c4a9c8 851 if (b->logic_data() && b->enabled()) {
47e9e7bb
SA
852 selector->addItem(b->name(),
853 qVariantFromValue((void*)b.get()));
5da5d081 854
9f97b357
SA
855 if (ch->assigned_signal == b.get())
856 selector->setCurrentIndex(selector->count() - 1);
4e5a4405
JH
857 }
858 }
859
860 return selector;
861}
862
9f97b357
SA
863QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
864 const DecodeChannel *ch)
407c9ebe
UH
865{
866 QComboBox *selector = new QComboBox(parent);
867
868 selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
869 selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
7df44935 870 selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
407c9ebe 871
9f97b357 872 selector->setCurrentIndex(ch->initial_pin_state);
407c9ebe
UH
873
874 selector->setToolTip("Initial (assumed) pin value before the first sample");
875
876 return selector;
877}
878
ad908057 879void DecodeTrace::on_new_annotations()
5b6ae103
SA
880{
881 if (!delayed_trace_updater_.isActive())
882 delayed_trace_updater_.start();
883}
884
885void DecodeTrace::on_delayed_trace_update()
9cef9567 886{
8dbbc7f0 887 if (owner_)
6e2c3c85 888 owner_->row_item_appearance_changed(false, true);
9cef9567
JH
889}
890
1b56c646
SA
891void DecodeTrace::on_decode_finished()
892{
893 if (owner_)
894 owner_->row_item_appearance_changed(false, true);
895}
896
b9329558 897void DecodeTrace::delete_pressed()
5ed1adf5
JH
898{
899 on_delete();
900}
901
b9329558 902void DecodeTrace::on_delete()
c51482b3 903{
ad908057 904 session_.remove_decode_signal(decode_signal_);
c51482b3
JH
905}
906
6ac6242b 907void DecodeTrace::on_channel_selected(int)
4e5a4405 908{
9f97b357
SA
909 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
910
911 // Determine signal that was selected
912 const data::SignalBase *signal =
913 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
914
915 // Determine decode channel ID this combo box is the channel selector for
916 const uint16_t id = channel_id_map_.at(cb);
917
918 decode_signal_->assign_signal(id, signal);
919}
920
921void DecodeTrace::on_channels_updated()
922{
923 if (owner_)
924 owner_->row_item_appearance_changed(false, true);
4e5a4405
JH
925}
926
9f97b357 927void DecodeTrace::on_init_state_changed(int)
407c9ebe 928{
9f97b357
SA
929 QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
930
931 // Determine inital pin state that was selected
932 int init_state = cb->itemData(cb->currentIndex()).value<int>();
933
934 // Determine decode channel ID this combo box is the channel selector for
935 const uint16_t id = init_state_map_.at(cb);
936
937 decode_signal_->set_initial_pin_state(id, init_state);
407c9ebe
UH
938}
939
7491a29f
JH
940void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
941{
ad908057 942 decode_signal_->stack_decoder(decoder);
37fd11b1
JH
943
944 create_popup_form();
7491a29f
JH
945}
946
613d097c
JH
947void DecodeTrace::on_delete_decoder(int index)
948{
ad908057 949 decode_signal_->remove_decoder(index);
613d097c
JH
950
951 // Update the popup
c063290a 952 create_popup_form();
613d097c
JH
953}
954
dd048a7e
JH
955void DecodeTrace::on_show_hide_decoder(int index)
956{
ad908057 957 const bool state = decode_signal_->toggle_decoder_visibility(index);
dd048a7e 958
8dbbc7f0 959 assert(index < (int)decoder_forms_.size());
ad908057 960 decoder_forms_[index]->set_decoder_visible(state);
dd048a7e 961
8dbbc7f0 962 if (owner_)
6e2c3c85 963 owner_->row_item_appearance_changed(false, true);
dd048a7e
JH
964}
965
1573bf16 966} // namespace trace
f4e57597 967} // namespace views
55d3603d 968} // namespace pv