]> sigrok.org Git - pulseview.git/blame_incremental - pv/view/view.cpp
Minor whitespace fixes.
[pulseview.git] / pv / view / view.cpp
... / ...
CommitLineData
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
21#ifdef ENABLE_DECODE
22#include <libsigrokdecode/libsigrokdecode.h>
23#endif
24
25#include <extdef.h>
26
27#include <algorithm>
28#include <cassert>
29#include <climits>
30#include <cmath>
31#include <iterator>
32#include <mutex>
33#include <unordered_set>
34
35#include <QApplication>
36#include <QEvent>
37#include <QFontMetrics>
38#include <QMouseEvent>
39#include <QScrollBar>
40
41#include <libsigrokcxx/libsigrokcxx.hpp>
42
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"
51
52#include "pv/session.hpp"
53#include "pv/devices/device.hpp"
54#include "pv/data/logic.hpp"
55#include "pv/data/logicsegment.hpp"
56#include "pv/util.hpp"
57
58using boost::shared_lock;
59using boost::shared_mutex;
60
61using pv::data::SignalData;
62using pv::data::Segment;
63using pv::util::format_time;
64
65using std::deque;
66using std::dynamic_pointer_cast;
67using std::inserter;
68using std::list;
69using std::lock_guard;
70using std::max;
71using std::make_pair;
72using std::min;
73using std::pair;
74using std::set;
75using std::set_difference;
76using std::shared_ptr;
77using std::unordered_map;
78using std::unordered_set;
79using std::vector;
80using std::weak_ptr;
81
82namespace pv {
83namespace view {
84
85const double View::MaxScale = 1e9;
86const double View::MinScale = 1e-15;
87
88const int View::MaxScrollValue = INT_MAX / 2;
89const int View::MaxViewAutoUpdateRate = 25; // No more than 25 Hz with sticky scrolling
90
91const int View::ScaleUnits[3] = {1, 2, 5};
92
93View::View(Session &session, QWidget *parent) :
94 QAbstractScrollArea(parent),
95 session_(session),
96 viewport_(new Viewport(*this)),
97 ruler_(new Ruler(*this)),
98 header_(new Header(*this)),
99 scale_(1e-3),
100 offset_(0),
101 updating_scroll_(false),
102 sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui()
103 always_zoom_to_fit_(false),
104 tick_period_(0.0),
105 tick_prefix_(0),
106 show_cursors_(false),
107 cursors_(new CursorPair(*this)),
108 next_flag_text_('A'),
109 hover_point_(-1, -1)
110{
111 connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
112 this, SLOT(h_scroll_value_changed(int)));
113 connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
114 this, SLOT(v_scroll_value_changed()));
115
116 connect(&session_, SIGNAL(signals_changed()),
117 this, SLOT(signals_changed()));
118 connect(&session_, SIGNAL(capture_state_changed(int)),
119 this, SLOT(data_updated()));
120 connect(&session_, SIGNAL(data_received()),
121 this, SLOT(data_updated()));
122 connect(&session_, SIGNAL(frame_ended()),
123 this, SLOT(data_updated()));
124
125 connect(header_, SIGNAL(selection_changed()),
126 ruler_, SLOT(clear_selection()));
127 connect(ruler_, SIGNAL(selection_changed()),
128 header_, SLOT(clear_selection()));
129
130 connect(header_, SIGNAL(selection_changed()),
131 this, SIGNAL(selection_changed()));
132 connect(ruler_, SIGNAL(selection_changed()),
133 this, SIGNAL(selection_changed()));
134
135 connect(this, SIGNAL(hover_point_changed()),
136 this, SLOT(on_hover_point_changed()));
137
138 connect(&lazy_event_handler_, SIGNAL(timeout()),
139 this, SLOT(process_sticky_events()));
140 lazy_event_handler_.setSingleShot(true);
141
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
147 setViewport(viewport_);
148
149 viewport_->installEventFilter(this);
150 ruler_->installEventFilter(this);
151 header_->installEventFilter(this);
152
153 // Trigger the initial event manually. The default device has signals
154 // which were created before this object came into being
155 signals_changed();
156
157 // make sure the transparent widgets are on the top
158 ruler_->raise();
159 header_->raise();
160
161 // Update the zoom state
162 calculate_tick_spacing();
163}
164
165Session& View::session()
166{
167 return session_;
168}
169
170const Session& View::session() const
171{
172 return session_;
173}
174
175View* View::view()
176{
177 return this;
178}
179
180const View* View::view() const
181{
182 return this;
183}
184
185Viewport* View::viewport()
186{
187 return viewport_;
188}
189
190const Viewport* View::viewport() const
191{
192 return viewport_;
193}
194
195vector< shared_ptr<TimeItem> > View::time_items() const
196{
197 const vector<shared_ptr<Flag>> f(flags());
198 vector<shared_ptr<TimeItem>> items(f.begin(), f.end());
199 items.push_back(cursors_);
200 items.push_back(cursors_->first());
201 items.push_back(cursors_->second());
202 return items;
203}
204
205double View::scale() const
206{
207 return scale_;
208}
209
210double View::offset() const
211{
212 return offset_;
213}
214
215int View::owner_visual_v_offset() const
216{
217 return -verticalScrollBar()->sliderPosition();
218}
219
220void View::set_v_offset(int offset)
221{
222 verticalScrollBar()->setSliderPosition(offset);
223 header_->update();
224 viewport_->update();
225}
226
227unsigned int View::depth() const
228{
229 return 0;
230}
231
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
242void View::zoom(double steps)
243{
244 zoom(steps, viewport_->width() / 2);
245}
246
247void View::zoom(double steps, int offset)
248{
249 set_zoom(scale_ * pow(3.0/2.0, -steps), offset);
250}
251
252void View::zoom_fit(bool gui_state)
253{
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
263 const pair<double, double> extents = get_time_extents();
264 const double delta = extents.second - extents.first;
265 if (delta < 1e-12)
266 return;
267
268 assert(viewport_);
269 const int w = viewport_->width();
270 if (w <= 0)
271 return;
272
273 const double scale = max(min(delta / w, MaxScale), MinScale);
274 set_scale_offset(scale, extents.first);
275}
276
277void View::zoom_one_to_one()
278{
279 using pv::data::SignalData;
280
281 // Make a set of all the visible data objects
282 set< shared_ptr<SignalData> > visible_data = get_visible_data();
283 if (visible_data.empty())
284 return;
285
286 double samplerate = 0.0;
287 for (const shared_ptr<SignalData> d : visible_data) {
288 assert(d);
289 const vector< shared_ptr<Segment> > segments =
290 d->segments();
291 for (const shared_ptr<Segment> &s : segments)
292 samplerate = max(samplerate, s->samplerate());
293 }
294
295 if (samplerate == 0.0)
296 return;
297
298 assert(viewport_);
299 const int w = viewport_->width();
300 if (w <= 0)
301 return;
302
303 set_zoom(1.0 / samplerate, w / 2);
304}
305
306void View::set_scale_offset(double scale, double offset)
307{
308 // Disable sticky scrolling / always zoom to fit when acquisition runs
309 // and user drags the viewport
310 if ((scale_ == scale) && (offset_ != offset) &&
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 }
322 }
323
324 scale_ = scale;
325 offset_ = offset;
326
327 calculate_tick_spacing();
328
329 update_scroll();
330 ruler_->update();
331 viewport_->update();
332 scale_offset_changed();
333}
334
335set< shared_ptr<SignalData> > View::get_visible_data() const
336{
337 shared_lock<shared_mutex> lock(session().signals_mutex());
338 const unordered_set< shared_ptr<Signal> > &sigs(session().signals());
339
340 // Make a set of all the visible data objects
341 set< shared_ptr<SignalData> > visible_data;
342 for (const shared_ptr<Signal> sig : sigs)
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{
351 double left_time = DBL_MAX, right_time = DBL_MIN;
352 const set< shared_ptr<SignalData> > visible_data = get_visible_data();
353 for (const shared_ptr<SignalData> d : visible_data)
354 {
355 const vector< shared_ptr<Segment> > segments =
356 d->segments();
357 for (const shared_ptr<Segment> &s : segments) {
358 double samplerate = s->samplerate();
359 samplerate = (samplerate <= 0.0) ? 1.0 : samplerate;
360
361 const double start_time = s->start_time();
362 left_time = min(left_time, start_time);
363 right_time = max(right_time, start_time +
364 d->max_sample_count() / samplerate);
365 }
366 }
367
368 if (left_time == DBL_MAX && right_time == DBL_MIN)
369 return make_pair(0.0, 0.0);
370
371 assert(left_time < right_time);
372 return make_pair(left_time, right_time);
373}
374
375void View::enable_sticky_scrolling(bool state)
376{
377 sticky_scrolling_ = state;
378}
379
380bool View::cursors_shown() const
381{
382 return show_cursors_;
383}
384
385void View::show_cursors(bool show)
386{
387 show_cursors_ = show;
388 ruler_->update();
389 viewport_->update();
390}
391
392void View::centre_cursors()
393{
394 const double time_width = scale_ * viewport_->width();
395 cursors_->first()->set_time(offset_ + time_width * 0.4);
396 cursors_->second()->set_time(offset_ + time_width * 0.6);
397 ruler_->update();
398 viewport_->update();
399}
400
401std::shared_ptr<CursorPair> View::cursors() const
402{
403 return cursors_;
404}
405
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
432const QPoint& View::hover_point() const
433{
434 return hover_point_;
435}
436
437void View::update_viewport()
438{
439 assert(viewport_);
440 viewport_->update();
441 header_->update();
442}
443
444void View::restack_all_row_items()
445{
446 // Make a list of owners that is sorted from deepest first
447 const auto owners = list_row_item_owners();
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();
460}
461
462void View::get_scroll_layout(double &length, double &offset) const
463{
464 const pair<double, double> extents = get_time_extents();
465 length = (extents.second - extents.first) / scale_;
466 offset = offset_ / scale_;
467}
468
469void View::set_zoom(double scale, int offset)
470{
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
475 const double cursor_offset = offset_ + scale_ * offset;
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
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
512 } while (typical_width > tick_period_ / scale_);
513}
514
515void View::update_scroll()
516{
517 assert(viewport_);
518
519 const QSize areaSize = viewport_->size();
520
521 // Set the horizontal scroll bar
522 double length = 0, offset = 0;
523 get_scroll_layout(length, offset);
524 length = max(length - areaSize.width(), 0.0);
525
526 int major_tick_distance = tick_period_ / scale_;
527
528 horizontalScrollBar()->setPageStep(areaSize.width() / 2);
529 horizontalScrollBar()->setSingleStep(major_tick_distance);
530
531 updating_scroll_ = true;
532
533 if (length < MaxScrollValue) {
534 horizontalScrollBar()->setRange(0, length);
535 horizontalScrollBar()->setSliderPosition(offset);
536 } else {
537 horizontalScrollBar()->setRange(0, MaxScrollValue);
538 horizontalScrollBar()->setSliderPosition(
539 offset_ * MaxScrollValue / (scale_ * length));
540 }
541
542 updating_scroll_ = false;
543
544 // Set the vertical scrollbar
545 verticalScrollBar()->setPageStep(areaSize.height());
546 verticalScrollBar()->setSingleStep(areaSize.height() / 8);
547
548 const pair<int, int> extents = v_extents();
549 verticalScrollBar()->setRange(extents.first - (areaSize.height() / 2),
550 extents.second - (areaSize.height() / 2));
551}
552
553void View::update_layout()
554{
555 setViewportMargins(
556 header_->sizeHint().width() - pv::view::Header::BaselineOffset,
557 ruler_->sizeHint().height(), 0, 0);
558 ruler_->setGeometry(viewport_->x(), 0,
559 viewport_->width(), ruler_->extended_size_hint().height());
560 header_->setGeometry(0, viewport_->y(),
561 header_->extended_size_hint().width(), viewport_->height());
562 update_scroll();
563}
564
565void View::paint_label(QPainter &p, const QRect &rect, bool hover)
566{
567 (void)p;
568 (void)rect;
569 (void)hover;
570}
571
572QRectF View::label_rect(const QRectF &rect)
573{
574 (void)rect;
575 return QRectF();
576}
577
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)
582{
583 assert(group);
584
585 unordered_set<RowItemOwner*> owners;
586 vector<RowItemOwner*> owner_list;
587
588 // Make a set and a list of all the owners
589 for (const auto &channel : group->channels()) {
590 const auto iter = signal_map.find(channel);
591 if (iter == signal_map.end())
592 continue;
593
594 RowItemOwner *const o = (*iter).second->owner();
595 owner_list.push_back(o);
596 owners.insert(o);
597 }
598
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 }
610 }
611
612 return prevalent_owner;
613}
614
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;
639}
640
641bool View::eventFilter(QObject *object, QEvent *event)
642{
643 const QEvent::Type type = event->type();
644 if (type == QEvent::MouseMove) {
645
646 const QMouseEvent *const mouse_event = (QMouseEvent*)event;
647 if (object == viewport_)
648 hover_point_ = mouse_event->pos();
649 else if (object == ruler_)
650 hover_point_ = QPoint(mouse_event->x(), 0);
651 else if (object == header_)
652 hover_point_ = QPoint(0, mouse_event->y());
653 else
654 hover_point_ = QPoint(-1, -1);
655
656 hover_point_changed();
657
658 } else if (type == QEvent::Leave) {
659 hover_point_ = QPoint(-1, -1);
660 hover_point_changed();
661 }
662
663 return QObject::eventFilter(object, event);
664}
665
666bool View::viewportEvent(QEvent *e)
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:
675 case QEvent::TouchBegin:
676 case QEvent::TouchUpdate:
677 case QEvent::TouchEnd:
678 return false;
679
680 default:
681 return QAbstractScrollArea::viewportEvent(e);
682 }
683}
684
685void View::resizeEvent(QResizeEvent*)
686{
687 update_layout();
688}
689
690void View::row_item_appearance_changed(bool label, bool content)
691{
692 if (label)
693 header_->update();
694 if (content)
695 viewport_->update();
696}
697
698void View::time_item_appearance_changed(bool label, bool content)
699{
700 if (label)
701 ruler_->update();
702 if (content)
703 viewport_->update();
704}
705
706void View::extents_changed(bool horz, bool vert)
707{
708 sticky_events_ |=
709 (horz ? RowItemHExtentsChanged : 0) |
710 (vert ? RowItemVExtentsChanged : 0);
711 lazy_event_handler_.start();
712}
713
714void View::h_scroll_value_changed(int value)
715{
716 if (updating_scroll_)
717 return;
718
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
726 const int range = horizontalScrollBar()->maximum();
727 if (range < MaxScrollValue)
728 offset_ = scale_ * value;
729 else {
730 double length = 0, offset;
731 get_scroll_layout(length, offset);
732 offset_ = scale_ * length * value / MaxScrollValue;
733 }
734
735 ruler_->update();
736 viewport_->update();
737}
738
739void View::v_scroll_value_changed()
740{
741 header_->update();
742 viewport_->update();
743}
744
745void View::signals_changed()
746{
747 vector< shared_ptr<RowItem> > new_top_level_items;
748
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);
755
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>();
759
760 shared_lock<shared_mutex> lock(session_.signals_mutex());
761 const unordered_set< shared_ptr<Signal> > &sigs(session_.signals());
762
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;
784 for (const shared_ptr<Signal> &sig : sigs)
785 signal_map[sig->channel()] = sig;
786
787 // Populate channel groups
788 for (auto entry : sr_dev->channel_groups())
789 {
790 const shared_ptr<sigrok::ChannelGroup> &group = entry.second;
791
792 if (group->channels().size() <= 1)
793 continue;
794
795 // Find best trace group to add to
796 RowItemOwner *owner = find_prevalent_trace_group(
797 group, signal_map);
798
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 }
805
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 }
826
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);
831 }
832
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 }
850
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;
863 }
864
865 update_layout();
866
867 header_->update();
868 viewport_->update();
869}
870
871void View::data_updated()
872{
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_) {
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{
891 if (always_zoom_to_fit_)
892 zoom_fit(true);
893
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 }
904
905 update_scroll();
906 ruler_->update();
907 viewport_->update();
908}
909
910void View::process_sticky_events()
911{
912 if (sticky_events_ & RowItemHExtentsChanged)
913 update_layout();
914 if (sticky_events_ & RowItemVExtentsChanged) {
915 restack_all_row_items();
916 update_scroll();
917 }
918
919 // Clear the sticky events
920 sticky_events_ = 0;
921}
922
923void View::on_hover_point_changed()
924{
925 for (shared_ptr<RowItem> r : *this)
926 r->hover_point_changed();
927}
928
929} // namespace view
930} // namespace pv