]> sigrok.org Git - pulseview.git/blame - pv/view/view.cpp
Minor whitespace fixes.
[pulseview.git] / pv / view / view.cpp
CommitLineData
adb4b10c 1/*
b3f22de0 2 * This file is part of the PulseView project.
adb4b10c
JH
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
269528f5 21#ifdef ENABLE_DECODE
4e5a4405 22#include <libsigrokdecode/libsigrokdecode.h>
269528f5 23#endif
4e5a4405 24
361c560e
JH
25#include <extdef.h>
26
cf124e47 27#include <algorithm>
c3a740dd
JH
28#include <cassert>
29#include <climits>
30#include <cmath>
cf124e47 31#include <iterator>
c3a740dd 32#include <mutex>
7ff0145f 33#include <unordered_set>
adb4b10c 34
361c560e 35#include <QApplication>
adb4b10c 36#include <QEvent>
361c560e 37#include <QFontMetrics>
cbd80f64 38#include <QMouseEvent>
adb4b10c
JH
39#include <QScrollBar>
40
fe3a1c21 41#include <libsigrokcxx/libsigrokcxx.hpp>
448a72cf 42
2acdb232
JH
43#include "decodetrace.hpp"
44#include "header.hpp"
45#include "logicsignal.hpp"
46#include "ruler.hpp"
47#include "signal.hpp"
48#include "tracegroup.hpp"
49#include "view.hpp"
50#include "viewport.hpp"
adb4b10c 51
f65cd27b 52#include "pv/session.hpp"
da30ecb7 53#include "pv/devices/device.hpp"
2acdb232 54#include "pv/data/logic.hpp"
f3d66e52 55#include "pv/data/logicsegment.hpp"
361c560e 56#include "pv/util.hpp"
adb4b10c 57
aca64cac
JH
58using boost::shared_lock;
59using boost::shared_mutex;
361c560e 60
1bc6525b 61using pv::data::SignalData;
f3d66e52 62using pv::data::Segment;
361c560e
JH
63using pv::util::format_time;
64
819f4c25 65using std::deque;
448a72cf 66using std::dynamic_pointer_cast;
cf124e47 67using std::inserter;
819f4c25 68using std::list;
c3a740dd 69using std::lock_guard;
819f4c25 70using std::max;
1bc6525b 71using std::make_pair;
819f4c25 72using std::min;
1bc6525b 73using std::pair;
819f4c25 74using std::set;
cf124e47 75using std::set_difference;
f9abf97e 76using std::shared_ptr;
448a72cf 77using std::unordered_map;
7ff0145f 78using std::unordered_set;
819f4c25 79using std::vector;
f9abf97e 80using std::weak_ptr;
adb4b10c 81
cdf7bea7
JH
82namespace pv {
83namespace view {
adb4b10c 84
cdf7bea7
JH
85const double View::MaxScale = 1e9;
86const double View::MinScale = 1e-15;
adb4b10c 87
f25770e2 88const int View::MaxScrollValue = INT_MAX / 2;
ce11b2ea 89const int View::MaxViewAutoUpdateRate = 25; // No more than 25 Hz with sticky scrolling
f25770e2 90
361c560e
JH
91const int View::ScaleUnits[3] = {1, 2, 5};
92
2b81ae46 93View::View(Session &session, QWidget *parent) :
adb4b10c 94 QAbstractScrollArea(parent),
8dbbc7f0
JH
95 session_(session),
96 viewport_(new Viewport(*this)),
97 ruler_(new Ruler(*this)),
8dbbc7f0 98 header_(new Header(*this)),
c0100b55 99 scale_(1e-3),
8dbbc7f0 100 offset_(0),
8dbbc7f0 101 updating_scroll_(false),
c7b03d9d 102 sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui()
ce11b2ea 103 always_zoom_to_fit_(false),
361c560e
JH
104 tick_period_(0.0),
105 tick_prefix_(0),
8dbbc7f0 106 show_cursors_(false),
5c5ce757 107 cursors_(new CursorPair(*this)),
8914fe79 108 next_flag_text_('A'),
8dbbc7f0 109 hover_point_(-1, -1)
adb4b10c 110{
b16907d3
JH
111 connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
112 this, SLOT(h_scroll_value_changed(int)));
adb4b10c 113 connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
f8400017 114 this, SLOT(v_scroll_value_changed()));
69dd2b03 115
8dbbc7f0 116 connect(&session_, SIGNAL(signals_changed()),
69dd2b03 117 this, SLOT(signals_changed()));
8dbbc7f0 118 connect(&session_, SIGNAL(capture_state_changed(int)),
50f97924 119 this, SLOT(data_updated()));
8dbbc7f0 120 connect(&session_, SIGNAL(data_received()),
1f374035 121 this, SLOT(data_updated()));
8dbbc7f0 122 connect(&session_, SIGNAL(frame_ended()),
adb4b10c 123 this, SLOT(data_updated()));
1d8dca91 124
8dbbc7f0 125 connect(header_, SIGNAL(selection_changed()),
819e2e95
JH
126 ruler_, SLOT(clear_selection()));
127 connect(ruler_, SIGNAL(selection_changed()),
8dbbc7f0 128 header_, SLOT(clear_selection()));
17348f85 129
8dbbc7f0 130 connect(header_, SIGNAL(selection_changed()),
8b454527 131 this, SIGNAL(selection_changed()));
819e2e95 132 connect(ruler_, SIGNAL(selection_changed()),
8b454527
JH
133 this, SIGNAL(selection_changed()));
134
33c62f44
JH
135 connect(this, SIGNAL(hover_point_changed()),
136 this, SLOT(on_hover_point_changed()));
137
8dbbc7f0 138 connect(&lazy_event_handler_, SIGNAL(timeout()),
32218d3e 139 this, SLOT(process_sticky_events()));
8dbbc7f0 140 lazy_event_handler_.setSingleShot(true);
32218d3e 141
c7b03d9d
SA
142 connect(&delayed_view_updater_, SIGNAL(timeout()),
143 this, SLOT(perform_delayed_view_update()));
144 delayed_view_updater_.setSingleShot(true);
145 delayed_view_updater_.setInterval(1000 / MaxViewAutoUpdateRate);
146
8dbbc7f0 147 setViewport(viewport_);
cbd80f64 148
8dbbc7f0
JH
149 viewport_->installEventFilter(this);
150 ruler_->installEventFilter(this);
8dbbc7f0 151 header_->installEventFilter(this);
d873f4d6 152
9f46d905
JH
153 // Trigger the initial event manually. The default device has signals
154 // which were created before this object came into being
d873f4d6 155 signals_changed();
84a0d458 156
512bfc56 157 // make sure the transparent widgets are on the top
819e2e95 158 ruler_->raise();
8dbbc7f0 159 header_->raise();
361c560e
JH
160
161 // Update the zoom state
162 calculate_tick_spacing();
adb4b10c
JH
163}
164
2b81ae46 165Session& View::session()
1d19ef83 166{
8dbbc7f0 167 return session_;
1d19ef83
JH
168}
169
2b81ae46 170const Session& View::session() const
38eeddea 171{
8dbbc7f0 172 return session_;
38eeddea
JH
173}
174
eae6e30a
JH
175View* View::view()
176{
177 return this;
178}
179
180const View* View::view() const
181{
182 return this;
183}
184
2ae445ba
SA
185Viewport* View::viewport()
186{
8dbbc7f0 187 return viewport_;
2ae445ba
SA
188}
189
190const Viewport* View::viewport() const
191{
8dbbc7f0 192 return viewport_;
2ae445ba
SA
193}
194
2496bf45
JH
195vector< shared_ptr<TimeItem> > View::time_items() const
196{
8914fe79
JH
197 const vector<shared_ptr<Flag>> f(flags());
198 vector<shared_ptr<TimeItem>> items(f.begin(), f.end());
5a3e53f6 199 items.push_back(cursors_);
5c5ce757
JH
200 items.push_back(cursors_->first());
201 items.push_back(cursors_->second());
2496bf45
JH
202 return items;
203}
204
cdf7bea7 205double View::scale() const
adb4b10c 206{
8dbbc7f0 207 return scale_;
adb4b10c
JH
208}
209
cdf7bea7 210double View::offset() const
adb4b10c 211{
8dbbc7f0 212 return offset_;
adb4b10c
JH
213}
214
7ff0145f 215int View::owner_visual_v_offset() const
adb4b10c 216{
f8400017 217 return -verticalScrollBar()->sliderPosition();
adb4b10c
JH
218}
219
4d476647
JH
220void View::set_v_offset(int offset)
221{
222 verticalScrollBar()->setSliderPosition(offset);
223 header_->update();
224 viewport_->update();
225}
226
3e769a37
JH
227unsigned int View::depth() const
228{
229 return 0;
230}
231
361c560e
JH
232unsigned int View::tick_prefix() const
233{
234 return tick_prefix_;
235}
236
237double View::tick_period() const
238{
239 return tick_period_;
240}
241
cdf7bea7 242void View::zoom(double steps)
adb4b10c 243{
8dbbc7f0 244 zoom(steps, viewport_->width() / 2);
adb4b10c
JH
245}
246
17c0f398
JH
247void View::zoom(double steps, int offset)
248{
8dbbc7f0 249 set_zoom(scale_ * pow(3.0/2.0, -steps), offset);
17c0f398
JH
250}
251
ce11b2ea 252void View::zoom_fit(bool gui_state)
ca46b534 253{
ce11b2ea
SA
254 // Act as one-shot when stopped, toggle along with the GUI otherwise
255 if (session_.get_capture_state() == Session::Stopped) {
256 always_zoom_to_fit_ = false;
257 always_zoom_to_fit_changed(false);
258 } else {
259 always_zoom_to_fit_ = gui_state;
260 always_zoom_to_fit_changed(gui_state);
261 }
262
1bc6525b
JH
263 const pair<double, double> extents = get_time_extents();
264 const double delta = extents.second - extents.first;
265 if (delta < 1e-12)
ca46b534
JH
266 return;
267
8dbbc7f0
JH
268 assert(viewport_);
269 const int w = viewport_->width();
ca46b534
JH
270 if (w <= 0)
271 return;
272
78311127
JH
273 const double scale = max(min(delta / w, MaxScale), MinScale);
274 set_scale_offset(scale, extents.first);
ca46b534
JH
275}
276
d1e7d82c
JH
277void View::zoom_one_to_one()
278{
279 using pv::data::SignalData;
280
d1e7d82c 281 // Make a set of all the visible data objects
0fc664a9 282 set< shared_ptr<SignalData> > visible_data = get_visible_data();
d1e7d82c
JH
283 if (visible_data.empty())
284 return;
285
286 double samplerate = 0.0;
d9aecf1f 287 for (const shared_ptr<SignalData> d : visible_data) {
d1e7d82c 288 assert(d);
f3d66e52
JH
289 const vector< shared_ptr<Segment> > segments =
290 d->segments();
291 for (const shared_ptr<Segment> &s : segments)
ff008de6 292 samplerate = max(samplerate, s->samplerate());
d1e7d82c
JH
293 }
294
295 if (samplerate == 0.0)
296 return;
297
8dbbc7f0
JH
298 assert(viewport_);
299 const int w = viewport_->width();
d1e7d82c
JH
300 if (w <= 0)
301 return;
302
303 set_zoom(1.0 / samplerate, w / 2);
304}
305
cdf7bea7 306void View::set_scale_offset(double scale, double offset)
adb4b10c 307{
ce11b2ea
SA
308 // Disable sticky scrolling / always zoom to fit when acquisition runs
309 // and user drags the viewport
c7b03d9d 310 if ((scale_ == scale) && (offset_ != offset) &&
ce11b2ea
SA
311 (session_.get_capture_state() == Session::Running)) {
312
313 if (sticky_scrolling_) {
314 sticky_scrolling_ = false;
315 sticky_scrolling_changed(false);
316 }
317
318 if (always_zoom_to_fit_) {
319 always_zoom_to_fit_ = false;
320 always_zoom_to_fit_changed(false);
321 }
c7b03d9d
SA
322 }
323
8dbbc7f0
JH
324 scale_ = scale;
325 offset_ = offset;
ccdd3ef5 326
361c560e
JH
327 calculate_tick_spacing();
328
adb4b10c 329 update_scroll();
8dbbc7f0 330 ruler_->update();
8dbbc7f0 331 viewport_->update();
e0fc5810 332 scale_offset_changed();
adb4b10c
JH
333}
334
1bc6525b
JH
335set< shared_ptr<SignalData> > View::get_visible_data() const
336{
aca64cac 337 shared_lock<shared_mutex> lock(session().signals_mutex());
78b0af3e 338 const unordered_set< shared_ptr<Signal> > &sigs(session().signals());
1bc6525b
JH
339
340 // Make a set of all the visible data objects
341 set< shared_ptr<SignalData> > visible_data;
d9aecf1f 342 for (const shared_ptr<Signal> sig : sigs)
1bc6525b
JH
343 if (sig->enabled())
344 visible_data.insert(sig->data());
345
346 return visible_data;
347}
348
349pair<double, double> View::get_time_extents() const
350{
1bc6525b 351 double left_time = DBL_MAX, right_time = DBL_MIN;
7f4038d6 352 const set< shared_ptr<SignalData> > visible_data = get_visible_data();
d9aecf1f 353 for (const shared_ptr<SignalData> d : visible_data)
1bc6525b 354 {
f3d66e52
JH
355 const vector< shared_ptr<Segment> > segments =
356 d->segments();
357 for (const shared_ptr<Segment> &s : segments) {
ff008de6
JH
358 double samplerate = s->samplerate();
359 samplerate = (samplerate <= 0.0) ? 1.0 : samplerate;
360
7f4038d6
JH
361 const double start_time = s->start_time();
362 left_time = min(left_time, start_time);
363 right_time = max(right_time, start_time +
e45b13b5 364 d->max_sample_count() / samplerate);
7f4038d6 365 }
1bc6525b
JH
366 }
367
7f4038d6
JH
368 if (left_time == DBL_MAX && right_time == DBL_MIN)
369 return make_pair(0.0, 0.0);
370
1bc6525b
JH
371 assert(left_time < right_time);
372 return make_pair(left_time, right_time);
373}
374
c7b03d9d
SA
375void View::enable_sticky_scrolling(bool state)
376{
377 sticky_scrolling_ = state;
378}
379
f76af637
JH
380bool View::cursors_shown() const
381{
8dbbc7f0 382 return show_cursors_;
f76af637
JH
383}
384
385void View::show_cursors(bool show)
386{
8dbbc7f0 387 show_cursors_ = show;
819e2e95 388 ruler_->update();
8dbbc7f0 389 viewport_->update();
f76af637
JH
390}
391
b4d91e56
JH
392void View::centre_cursors()
393{
8dbbc7f0 394 const double time_width = scale_ * viewport_->width();
5c5ce757
JH
395 cursors_->first()->set_time(offset_ + time_width * 0.4);
396 cursors_->second()->set_time(offset_ + time_width * 0.6);
819e2e95 397 ruler_->update();
8dbbc7f0 398 viewport_->update();
b4d91e56
JH
399}
400
5c5ce757 401std::shared_ptr<CursorPair> View::cursors() const
58864c5c 402{
8dbbc7f0 403 return cursors_;
58864c5c
JH
404}
405
8914fe79
JH
406void View::add_flag(double time)
407{
408 flags_.push_back(shared_ptr<Flag>(new Flag(*this, time,
409 QString("%1").arg(next_flag_text_))));
410 next_flag_text_ = (next_flag_text_ >= 'Z') ? 'A' :
411 (next_flag_text_ + 1);
412 time_item_appearance_changed(true, true);
413}
414
415void View::remove_flag(std::shared_ptr<Flag> flag)
416{
417 flags_.remove(flag);
418 time_item_appearance_changed(true, true);
419}
420
421vector< std::shared_ptr<Flag> > View::flags() const
422{
423 vector< std::shared_ptr<Flag> > flags(flags_.begin(), flags_.end());
424 stable_sort(flags.begin(), flags.end(),
425 [](const shared_ptr<Flag> &a, const shared_ptr<Flag> &b) {
426 return a->time() < b->time();
427 });
428
429 return flags;
430}
431
cbd80f64
JH
432const QPoint& View::hover_point() const
433{
8dbbc7f0 434 return hover_point_;
cbd80f64
JH
435}
436
9cef9567
JH
437void View::update_viewport()
438{
8dbbc7f0
JH
439 assert(viewport_);
440 viewport_->update();
441 header_->update();
7ff0145f
JH
442}
443
444void View::restack_all_row_items()
445{
873cbed0
JH
446 // Make a list of owners that is sorted from deepest first
447 const auto owners = list_row_item_owners();
7ff0145f
JH
448 vector< RowItemOwner* > sorted_owners(owners.begin(), owners.end());
449 sort(sorted_owners.begin(), sorted_owners.end(),
450 [](const RowItemOwner* a, const RowItemOwner *b) {
451 return a->depth() > b->depth(); });
452
453 // Restack the items recursively
454 for (auto &o : sorted_owners)
455 o->restack_items();
456
457 // Animate the items to their destination
458 for (const auto &r : *this)
459 r->animate_to_layout_v_offset();
9cef9567
JH
460}
461
f25770e2
JH
462void View::get_scroll_layout(double &length, double &offset) const
463{
1bc6525b 464 const pair<double, double> extents = get_time_extents();
8dbbc7f0
JH
465 length = (extents.second - extents.first) / scale_;
466 offset = offset_ / scale_;
f25770e2
JH
467}
468
d1e7d82c
JH
469void View::set_zoom(double scale, int offset)
470{
ce11b2ea
SA
471 // Reset the "always zoom to fit" feature as the user changed the zoom
472 always_zoom_to_fit_ = false;
473 always_zoom_to_fit_changed(false);
474
8dbbc7f0 475 const double cursor_offset = offset_ + scale_ * offset;
d1e7d82c
JH
476 const double new_scale = max(min(scale, MaxScale), MinScale);
477 const double new_offset = cursor_offset - new_scale * offset;
478 set_scale_offset(new_scale, new_offset);
479}
480
361c560e
JH
481void View::calculate_tick_spacing()
482{
483 const double SpacingIncrement = 32.0f;
484 const double MinValueSpacing = 32.0f;
485
486 double min_width = SpacingIncrement, typical_width;
487
488 QFontMetrics m(QApplication::font());
489
490 do {
491 const double min_period = scale_ * min_width;
492
493 const int order = (int)floorf(log10f(min_period));
494 const double order_decimal = pow(10.0, order);
495
496 unsigned int unit = 0;
497
498 do {
499 tick_period_ = order_decimal * ScaleUnits[unit++];
500 } while (tick_period_ < min_period &&
501 unit < countof(ScaleUnits));
502
503 tick_prefix_ = (order - pv::util::FirstSIPrefixPower) / 3;
504
505 typical_width = m.boundingRect(0, 0, INT_MAX, INT_MAX,
506 Qt::AlignLeft | Qt::AlignTop,
507 format_time(offset_, tick_prefix_)).width() +
508 MinValueSpacing;
509
510 min_width += SpacingIncrement;
511
f3290553 512 } while (typical_width > tick_period_ / scale_);
361c560e
JH
513}
514
cdf7bea7 515void View::update_scroll()
adb4b10c 516{
8dbbc7f0 517 assert(viewport_);
adb4b10c 518
8dbbc7f0 519 const QSize areaSize = viewport_->size();
adb4b10c
JH
520
521 // Set the horizontal scroll bar
522 double length = 0, offset = 0;
f25770e2
JH
523 get_scroll_layout(length, offset);
524 length = max(length - areaSize.width(), 0.0);
adb4b10c 525
d7aae647
SA
526 int major_tick_distance = tick_period_ / scale_;
527
b4ef7f2a 528 horizontalScrollBar()->setPageStep(areaSize.width() / 2);
d7aae647 529 horizontalScrollBar()->setSingleStep(major_tick_distance);
f25770e2 530
8dbbc7f0 531 updating_scroll_ = true;
528bd8a1 532
333d5bbc 533 if (length < MaxScrollValue) {
f25770e2
JH
534 horizontalScrollBar()->setRange(0, length);
535 horizontalScrollBar()->setSliderPosition(offset);
536 } else {
537 horizontalScrollBar()->setRange(0, MaxScrollValue);
538 horizontalScrollBar()->setSliderPosition(
8dbbc7f0 539 offset_ * MaxScrollValue / (scale_ * length));
f25770e2 540 }
adb4b10c 541
8dbbc7f0 542 updating_scroll_ = false;
528bd8a1 543
adb4b10c
JH
544 // Set the vertical scrollbar
545 verticalScrollBar()->setPageStep(areaSize.height());
65c34596 546 verticalScrollBar()->setSingleStep(areaSize.height() / 8);
a5d93c27
JH
547
548 const pair<int, int> extents = v_extents();
f1d9ac67
SA
549 verticalScrollBar()->setRange(extents.first - (areaSize.height() / 2),
550 extents.second - (areaSize.height() / 2));
adb4b10c
JH
551}
552
d7c0ca4a
JH
553void View::update_layout()
554{
512bfc56 555 setViewportMargins(
8dbbc7f0
JH
556 header_->sizeHint().width() - pv::view::Header::BaselineOffset,
557 ruler_->sizeHint().height(), 0, 0);
558 ruler_->setGeometry(viewport_->x(), 0,
819e2e95 559 viewport_->width(), ruler_->extended_size_hint().height());
8dbbc7f0 560 header_->setGeometry(0, viewport_->y(),
819e2e95 561 header_->extended_size_hint().width(), viewport_->height());
d7c0ca4a
JH
562 update_scroll();
563}
564
b3f44329 565void View::paint_label(QPainter &p, const QRect &rect, bool hover)
eae6e30a
JH
566{
567 (void)p;
b3f44329 568 (void)rect;
eae6e30a
JH
569 (void)hover;
570}
571
b3f44329 572QRectF View::label_rect(const QRectF &rect)
eae6e30a 573{
b3f44329 574 (void)rect;
eae6e30a
JH
575 return QRectF();
576}
577
cf124e47
JH
578RowItemOwner* View::find_prevalent_trace_group(
579 const shared_ptr<sigrok::ChannelGroup> &group,
580 const unordered_map<shared_ptr<sigrok::Channel>, shared_ptr<Signal> >
581 &signal_map)
448a72cf 582{
cf124e47 583 assert(group);
448a72cf 584
cf124e47
JH
585 unordered_set<RowItemOwner*> owners;
586 vector<RowItemOwner*> owner_list;
448a72cf 587
cf124e47
JH
588 // Make a set and a list of all the owners
589 for (const auto &channel : group->channels()) {
448a72cf 590 const auto iter = signal_map.find(channel);
cf124e47 591 if (iter == signal_map.end())
448a72cf
JH
592 continue;
593
cf124e47
JH
594 RowItemOwner *const o = (*iter).second->owner();
595 owner_list.push_back(o);
596 owners.insert(o);
597 }
448a72cf 598
cf124e47
JH
599 // Iterate through the list of owners, and find the most prevalent
600 size_t max_prevalence = 0;
601 RowItemOwner *prevalent_owner = nullptr;
602 for (RowItemOwner *owner : owners) {
603 const size_t prevalence = std::count_if(
604 owner_list.begin(), owner_list.end(),
605 [&](RowItemOwner *o) { return o == owner; });
606 if (prevalence > max_prevalence) {
607 max_prevalence = prevalence;
608 prevalent_owner = owner;
609 }
448a72cf
JH
610 }
611
cf124e47 612 return prevalent_owner;
448a72cf
JH
613}
614
cf124e47
JH
615vector< shared_ptr<Trace> > View::extract_new_traces_for_channels(
616 const vector< shared_ptr<sigrok::Channel> > &channels,
617 const unordered_map<shared_ptr<sigrok::Channel>, shared_ptr<Signal> >
618 &signal_map,
619 set< shared_ptr<Trace> > &add_list)
620{
621 vector< shared_ptr<Trace> > filtered_traces;
622
623 for (const auto &channel : channels)
624 {
625 const auto map_iter = signal_map.find(channel);
626 if (map_iter == signal_map.end())
627 continue;
628
629 shared_ptr<Trace> trace = (*map_iter).second;
630 const auto list_iter = add_list.find(trace);
631 if (list_iter == add_list.end())
632 continue;
633
634 filtered_traces.push_back(trace);
635 add_list.erase(list_iter);
636 }
637
638 return filtered_traces;
448a72cf
JH
639}
640
cbd80f64
JH
641bool View::eventFilter(QObject *object, QEvent *event)
642{
643 const QEvent::Type type = event->type();
333d5bbc 644 if (type == QEvent::MouseMove) {
cbd80f64
JH
645
646 const QMouseEvent *const mouse_event = (QMouseEvent*)event;
8dbbc7f0
JH
647 if (object == viewport_)
648 hover_point_ = mouse_event->pos();
819e2e95 649 else if (object == ruler_)
8dbbc7f0
JH
650 hover_point_ = QPoint(mouse_event->x(), 0);
651 else if (object == header_)
652 hover_point_ = QPoint(0, mouse_event->y());
cbd80f64 653 else
8dbbc7f0 654 hover_point_ = QPoint(-1, -1);
cbd80f64
JH
655
656 hover_point_changed();
657
333d5bbc 658 } else if (type == QEvent::Leave) {
8dbbc7f0 659 hover_point_ = QPoint(-1, -1);
cbd80f64
JH
660 hover_point_changed();
661 }
662
663 return QObject::eventFilter(object, event);
664}
665
cdf7bea7 666bool View::viewportEvent(QEvent *e)
adb4b10c
JH
667{
668 switch(e->type()) {
669 case QEvent::Paint:
670 case QEvent::MouseButtonPress:
671 case QEvent::MouseButtonRelease:
672 case QEvent::MouseButtonDblClick:
673 case QEvent::MouseMove:
674 case QEvent::Wheel:
4b4f1c0d
MC
675 case QEvent::TouchBegin:
676 case QEvent::TouchUpdate:
677 case QEvent::TouchEnd:
adb4b10c
JH
678 return false;
679
680 default:
681 return QAbstractScrollArea::viewportEvent(e);
682 }
683}
684
e314eca4 685void View::resizeEvent(QResizeEvent*)
adb4b10c 686{
d7c0ca4a 687 update_layout();
adb4b10c
JH
688}
689
6e2c3c85 690void View::row_item_appearance_changed(bool label, bool content)
32218d3e
JH
691{
692 if (label)
8dbbc7f0 693 header_->update();
32218d3e 694 if (content)
8dbbc7f0 695 viewport_->update();
32218d3e
JH
696}
697
98cfe4e8
JH
698void View::time_item_appearance_changed(bool label, bool content)
699{
700 if (label)
819e2e95 701 ruler_->update();
98cfe4e8
JH
702 if (content)
703 viewport_->update();
704}
705
32218d3e
JH
706void View::extents_changed(bool horz, bool vert)
707{
8dbbc7f0 708 sticky_events_ |=
76fea660
JH
709 (horz ? RowItemHExtentsChanged : 0) |
710 (vert ? RowItemVExtentsChanged : 0);
8dbbc7f0 711 lazy_event_handler_.start();
32218d3e
JH
712}
713
b16907d3 714void View::h_scroll_value_changed(int value)
adb4b10c 715{
8dbbc7f0 716 if (updating_scroll_)
528bd8a1
JH
717 return;
718
c7b03d9d
SA
719 // Disable sticky scrolling when user moves the horizontal scroll bar
720 // during a running acquisition
721 if (sticky_scrolling_ && (session_.get_capture_state() == Session::Running)) {
722 sticky_scrolling_ = false;
723 sticky_scrolling_changed(false);
724 }
725
f25770e2 726 const int range = horizontalScrollBar()->maximum();
333d5bbc 727 if (range < MaxScrollValue)
8dbbc7f0 728 offset_ = scale_ * value;
f25770e2
JH
729 else {
730 double length = 0, offset;
731 get_scroll_layout(length, offset);
8dbbc7f0 732 offset_ = scale_ * length * value / MaxScrollValue;
f25770e2
JH
733 }
734
8dbbc7f0 735 ruler_->update();
8dbbc7f0 736 viewport_->update();
adb4b10c
JH
737}
738
f8400017 739void View::v_scroll_value_changed()
adb4b10c 740{
8dbbc7f0
JH
741 header_->update();
742 viewport_->update();
adb4b10c
JH
743}
744
69dd2b03
JH
745void View::signals_changed()
746{
cf124e47 747 vector< shared_ptr<RowItem> > new_top_level_items;
68b21a71 748
e37e05f7
JH
749 const auto device = session_.device();
750 if (!device)
751 return;
752
753 shared_ptr<sigrok::Device> sr_dev = device->device();
754 assert(sr_dev);
448a72cf 755
cf124e47
JH
756 // Make a list of traces that are being added, and a list of traces
757 // that are being removed
758 const set<shared_ptr<Trace>> prev_traces = list_by_type<Trace>();
448a72cf 759
8dbbc7f0 760 shared_lock<shared_mutex> lock(session_.signals_mutex());
78b0af3e 761 const unordered_set< shared_ptr<Signal> > &sigs(session_.signals());
448a72cf 762
cf124e47
JH
763 set< shared_ptr<Trace> > traces(sigs.begin(), sigs.end());
764
765#ifdef ENABLE_DECODE
766 const vector< shared_ptr<DecodeTrace> > decode_traces(
767 session().get_decode_signals());
768 traces.insert(decode_traces.begin(), decode_traces.end());
769#endif
770
771 set< shared_ptr<Trace> > add_traces;
772 set_difference(traces.begin(), traces.end(),
773 prev_traces.begin(), prev_traces.end(),
774 inserter(add_traces, add_traces.begin()));
775
776 set< shared_ptr<Trace> > remove_traces;
777 set_difference(prev_traces.begin(), prev_traces.end(),
778 traces.begin(), traces.end(),
779 inserter(remove_traces, remove_traces.begin()));
780
781 // Make a look-up table of sigrok Channels to pulseview Signals
782 unordered_map<shared_ptr<sigrok::Channel>, shared_ptr<Signal> >
783 signal_map;
448a72cf
JH
784 for (const shared_ptr<Signal> &sig : sigs)
785 signal_map[sig->channel()] = sig;
786
787 // Populate channel groups
e37e05f7 788 for (auto entry : sr_dev->channel_groups())
448a72cf
JH
789 {
790 const shared_ptr<sigrok::ChannelGroup> &group = entry.second;
791
792 if (group->channels().size() <= 1)
793 continue;
794
cf124e47
JH
795 // Find best trace group to add to
796 RowItemOwner *owner = find_prevalent_trace_group(
797 group, signal_map);
448a72cf 798
cf124e47
JH
799 // If there is no trace group, create one
800 shared_ptr<TraceGroup> new_trace_group;
801 if (!owner) {
802 new_trace_group.reset(new TraceGroup());
803 owner = new_trace_group.get();
804 }
448a72cf 805
cf124e47
JH
806 // Extract traces for the trace group, removing them from
807 // the add list
808 const vector< shared_ptr<Trace> > new_traces_in_group =
809 extract_new_traces_for_channels(group->channels(),
810 signal_map, add_traces);
811
812 // Add the traces to the group
813 const pair<int, int> prev_v_extents = owner->v_extents();
814 int offset = prev_v_extents.second - prev_v_extents.first;
815 for (shared_ptr<Trace> trace : new_traces_in_group) {
816 assert(trace);
817 owner->add_child_item(trace);
818
819 const pair<int, int> extents = trace->v_extents();
820 if (trace->enabled())
821 offset += -extents.first;
822 trace->force_to_v_offset(offset);
823 if (trace->enabled())
824 offset += extents.second;
825 }
68b21a71 826
cf124e47
JH
827 // If this is a new group, enqueue it in the new top level
828 // items list
829 if (!new_traces_in_group.empty() && new_trace_group)
830 new_top_level_items.push_back(new_trace_group);
448a72cf
JH
831 }
832
cf124e47
JH
833 // Enqueue the remaining channels as free ungrouped traces
834 const vector< shared_ptr<Trace> > new_top_level_signals =
835 extract_new_traces_for_channels(sr_dev->channels(),
836 signal_map, add_traces);
837 new_top_level_items.insert(new_top_level_items.end(),
838 new_top_level_signals.begin(), new_top_level_signals.end());
839
840 // Enqueue any remaining traces i.e. decode traces
841 new_top_level_items.insert(new_top_level_items.end(),
842 add_traces.begin(), add_traces.end());
843
844 // Remove any removed traces
845 for (shared_ptr<Trace> trace : remove_traces) {
846 RowItemOwner *const owner = trace->owner();
847 assert(owner);
848 owner->remove_child_item(trace);
849 }
448a72cf 850
cf124e47
JH
851 // Add and position the pending top levels items
852 for (auto item : new_top_level_items) {
853 add_child_item(item);
854
855 // Position the item after the last present item
856 int offset = v_extents().second;
857 const pair<int, int> extents = item->v_extents();
858 if (item->enabled())
859 offset += -extents.first;
860 item->force_to_v_offset(offset);
861 if (item->enabled())
862 offset += extents.second;
ef8311a4
JH
863 }
864
a6c1726e 865 update_layout();
cf124e47
JH
866
867 header_->update();
868 viewport_->update();
69dd2b03
JH
869}
870
cdf7bea7 871void View::data_updated()
adb4b10c 872{
ce11b2ea
SA
873 // Reset "always zoom to fit" when we change to the stopped state
874 if (always_zoom_to_fit_ && (session_.get_capture_state() == Session::Stopped)) {
875 always_zoom_to_fit_ = false;
876 always_zoom_to_fit_changed(false);
877 }
878
879 if (always_zoom_to_fit_ || sticky_scrolling_) {
c7b03d9d
SA
880 if (!delayed_view_updater_.isActive())
881 delayed_view_updater_.start();
882 } else {
883 update_scroll();
884 ruler_->update();
885 viewport_->update();
886 }
887}
888
889void View::perform_delayed_view_update()
890{
ce11b2ea
SA
891 if (always_zoom_to_fit_)
892 zoom_fit(true);
893
c7b03d9d
SA
894 if (sticky_scrolling_) {
895 // Make right side of the view sticky
896 double length = 0, offset;
897 get_scroll_layout(length, offset);
898
899 const QSize areaSize = viewport_->size();
900 length = max(length - areaSize.width(), 0.0);
901
902 offset_ = scale_ * length;
903 }
adb4b10c 904
c7b03d9d
SA
905 update_scroll();
906 ruler_->update();
8dbbc7f0 907 viewport_->update();
adb4b10c 908}
cdf7bea7 909
32218d3e 910void View::process_sticky_events()
d7c0ca4a 911{
76fea660 912 if (sticky_events_ & RowItemHExtentsChanged)
32218d3e 913 update_layout();
f15bb3bc 914 if (sticky_events_ & RowItemVExtentsChanged) {
7ff0145f 915 restack_all_row_items();
f15bb3bc
JH
916 update_scroll();
917 }
32218d3e
JH
918
919 // Clear the sticky events
8dbbc7f0 920 sticky_events_ = 0;
d7c0ca4a
JH
921}
922
33c62f44
JH
923void View::on_hover_point_changed()
924{
aa59d5c2 925 for (shared_ptr<RowItem> r : *this)
eae6e30a 926 r->hover_point_changed();
33c62f44
JH
927}
928
cdf7bea7
JH
929} // namespace view
930} // namespace pv