]> sigrok.org Git - pulseview.git/blob - pv/views/trace/view.cpp
972b379d9dad1682318da2be20aa021999c2f109
[pulseview.git] / pv / views / trace / view.cpp
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, see <http://www.gnu.org/licenses/>.
18  */
19
20 #ifdef ENABLE_DECODE
21 #include <libsigrokdecode/libsigrokdecode.h>
22 #endif
23
24 #include <extdef.h>
25
26 #include <algorithm>
27 #include <cassert>
28 #include <climits>
29 #include <cmath>
30 #include <iostream>
31 #include <iterator>
32 #include <unordered_set>
33
34 #include <boost/archive/text_iarchive.hpp>
35 #include <boost/archive/text_oarchive.hpp>
36 #include <boost/serialization/serialization.hpp>
37
38 #include <QApplication>
39 #include <QEvent>
40 #include <QFontMetrics>
41 #include <QMouseEvent>
42 #include <QScrollBar>
43 #include <QVBoxLayout>
44
45 #include <libsigrokcxx/libsigrokcxx.hpp>
46
47 #include "analogsignal.hpp"
48 #include "header.hpp"
49 #include "logicsignal.hpp"
50 #include "ruler.hpp"
51 #include "signal.hpp"
52 #include "tracegroup.hpp"
53 #include "triggermarker.hpp"
54 #include "view.hpp"
55 #include "viewport.hpp"
56
57 #include "pv/data/logic.hpp"
58 #include "pv/data/logicsegment.hpp"
59 #include "pv/devices/device.hpp"
60 #include "pv/globalsettings.hpp"
61 #include "pv/session.hpp"
62 #include "pv/util.hpp"
63
64 #ifdef ENABLE_DECODE
65 #include "decodetrace.hpp"
66 #endif
67
68 using pv::data::SignalData;
69 using pv::data::Segment;
70 using pv::util::TimeUnit;
71 using pv::util::Timestamp;
72
73 using std::back_inserter;
74 using std::copy_if;
75 using std::count_if;
76 using std::dynamic_pointer_cast;
77 using std::inserter;
78 using std::max;
79 using std::make_pair;
80 using std::make_shared;
81 using std::min;
82 using std::pair;
83 using std::set;
84 using std::set_difference;
85 using std::shared_ptr;
86 using std::stringstream;
87 using std::unordered_map;
88 using std::unordered_set;
89 using std::vector;
90
91 namespace pv {
92 namespace views {
93 namespace trace {
94
95 const Timestamp View::MaxScale("1e9");
96 const Timestamp View::MinScale("1e-12");
97
98 const int View::MaxScrollValue = INT_MAX / 2;
99
100 const int View::ScaleUnits[3] = {1, 2, 5};
101
102
103 CustomScrollArea::CustomScrollArea(QWidget *parent) :
104         QAbstractScrollArea(parent)
105 {
106 }
107
108 bool CustomScrollArea::viewportEvent(QEvent *event)
109 {
110         switch (event->type()) {
111         case QEvent::Paint:
112         case QEvent::MouseButtonPress:
113         case QEvent::MouseButtonRelease:
114         case QEvent::MouseButtonDblClick:
115         case QEvent::MouseMove:
116         case QEvent::Wheel:
117         case QEvent::TouchBegin:
118         case QEvent::TouchUpdate:
119         case QEvent::TouchEnd:
120                 return false;
121         default:
122                 return QAbstractScrollArea::viewportEvent(event);
123         }
124 }
125
126 View::View(Session &session, bool is_main_view, QWidget *parent) :
127         ViewBase(session, is_main_view, parent),
128         splitter_(new QSplitter()),
129         scale_(1e-3),
130         offset_(0),
131         updating_scroll_(false),
132         settings_restored_(false),
133         sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui()
134         always_zoom_to_fit_(false),
135         tick_period_(0),
136         tick_prefix_(pv::util::SIPrefix::yocto),
137         tick_precision_(0),
138         time_unit_(util::TimeUnit::Time),
139         show_cursors_(false),
140         cursors_(new CursorPair(*this)),
141         next_flag_text_('A'),
142         trigger_markers_(),
143         hover_point_(-1, -1),
144         scroll_needs_defaults_(true),
145         saved_v_offset_(0),
146         scale_at_acq_start_(0),
147         offset_at_acq_start_(0),
148         suppress_zoom_to_fit_after_acq_(false)
149 {
150         QVBoxLayout *root_layout = new QVBoxLayout(this);
151         root_layout->setContentsMargins(0, 0, 0, 0);
152         root_layout->addWidget(splitter_);
153
154         viewport_ = new Viewport(*this);
155         scrollarea_ = new CustomScrollArea(splitter_);
156         scrollarea_->setViewport(viewport_);
157         scrollarea_->setFrameShape(QFrame::NoFrame);
158
159         ruler_ = new Ruler(*this);
160
161         header_ = new Header(*this);
162         header_->setMinimumWidth(10);  // So that the arrow tips show at least
163
164         // We put the header into a simple layout so that we can add the top margin,
165         // allowing us to make it line up with the bottom of the ruler
166         QWidget *header_container = new QWidget();
167         header_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
168         QVBoxLayout *header_layout = new QVBoxLayout(header_container);
169         header_layout->setContentsMargins(0, ruler_->sizeHint().height(), 0, 0);
170         header_layout->addWidget(header_);
171
172         // To let the ruler and scrollarea be on the same split pane, we need a layout
173         QWidget *trace_container = new QWidget();
174         trace_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
175         QVBoxLayout *trace_layout = new QVBoxLayout(trace_container);
176         trace_layout->setSpacing(0);  // We don't want space between the ruler and scrollarea
177         trace_layout->setContentsMargins(0, 0, 0, 0);
178         trace_layout->addWidget(ruler_);
179         trace_layout->addWidget(scrollarea_);
180
181         splitter_->addWidget(header_container);
182         splitter_->addWidget(trace_container);
183         splitter_->setHandleWidth(1);  // Don't show a visible rubber band
184         splitter_->setCollapsible(0, false);  // Prevent the header from collapsing
185         splitter_->setCollapsible(1, false);  // Prevent the traces from collapsing
186         splitter_->setStretchFactor(0, 0);  // Prevent the panes from being resized
187         splitter_->setStretchFactor(1, 1);  // when the entire view is resized
188         splitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
189
190         viewport_->installEventFilter(this);
191         ruler_->installEventFilter(this);
192         header_->installEventFilter(this);
193
194         // Set up settings and event handlers
195         GlobalSettings settings;
196         coloured_bg_ = settings.value(GlobalSettings::Key_View_ColouredBG).toBool();
197
198         connect(scrollarea_->horizontalScrollBar(), SIGNAL(valueChanged(int)),
199                 this, SLOT(h_scroll_value_changed(int)));
200         connect(scrollarea_->verticalScrollBar(), SIGNAL(valueChanged(int)),
201                 this, SLOT(v_scroll_value_changed()));
202
203         connect(header_, SIGNAL(selection_changed()),
204                 ruler_, SLOT(clear_selection()));
205         connect(ruler_, SIGNAL(selection_changed()),
206                 header_, SLOT(clear_selection()));
207
208         connect(header_, SIGNAL(selection_changed()),
209                 this, SIGNAL(selection_changed()));
210         connect(ruler_, SIGNAL(selection_changed()),
211                 this, SIGNAL(selection_changed()));
212
213         connect(splitter_, SIGNAL(splitterMoved(int, int)),
214                 this, SLOT(on_splitter_moved()));
215
216         connect(this, SIGNAL(hover_point_changed()),
217                 this, SLOT(on_hover_point_changed()));
218
219         connect(&lazy_event_handler_, SIGNAL(timeout()),
220                 this, SLOT(process_sticky_events()));
221         lazy_event_handler_.setSingleShot(true);
222
223         // Trigger the initial event manually. The default device has signals
224         // which were created before this object came into being
225         signals_changed();
226
227         // make sure the transparent widgets are on the top
228         ruler_->raise();
229         header_->raise();
230
231         // Update the zoom state
232         calculate_tick_spacing();
233 }
234
235 Session& View::session()
236 {
237         return session_;
238 }
239
240 const Session& View::session() const
241 {
242         return session_;
243 }
244
245 unordered_set< shared_ptr<Signal> > View::signals() const
246 {
247         return signals_;
248 }
249
250 void View::clear_signals()
251 {
252         ViewBase::clear_signalbases();
253         signals_.clear();
254 }
255
256 void View::add_signal(const shared_ptr<Signal> signal)
257 {
258         ViewBase::add_signalbase(signal->base());
259         signals_.insert(signal);
260 }
261
262 #ifdef ENABLE_DECODE
263 void View::clear_decode_signals()
264 {
265         decode_traces_.clear();
266 }
267
268 void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
269 {
270         shared_ptr<DecodeTrace> d(
271                 new DecodeTrace(session_, signal, decode_traces_.size()));
272         decode_traces_.push_back(d);
273 }
274
275 void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
276 {
277         for (auto i = decode_traces_.begin(); i != decode_traces_.end(); i++)
278                 if ((*i)->base() == signal) {
279                         decode_traces_.erase(i);
280                         signals_changed();
281                         return;
282                 }
283 }
284 #endif
285
286 View* View::view()
287 {
288         return this;
289 }
290
291 const View* View::view() const
292 {
293         return this;
294 }
295
296 Viewport* View::viewport()
297 {
298         return viewport_;
299 }
300
301 const Viewport* View::viewport() const
302 {
303         return viewport_;
304 }
305
306 void View::save_settings(QSettings &settings) const
307 {
308         settings.setValue("scale", scale_);
309         settings.setValue("v_offset",
310                 scrollarea_->verticalScrollBar()->sliderPosition());
311
312         settings.setValue("splitter_state", splitter_->saveState());
313
314         stringstream ss;
315         boost::archive::text_oarchive oa(ss);
316         oa << boost::serialization::make_nvp("offset", offset_);
317         settings.setValue("offset", QString::fromStdString(ss.str()));
318
319         for (shared_ptr<Signal> signal : signals_) {
320                 settings.beginGroup(signal->base()->internal_name());
321                 signal->save_settings(settings);
322                 settings.endGroup();
323         }
324 }
325
326 void View::restore_settings(QSettings &settings)
327 {
328         // Note: It is assumed that this function is only called once,
329         // immediately after restoring a previous session.
330
331         if (settings.contains("scale"))
332                 set_scale(settings.value("scale").toDouble());
333
334         if (settings.contains("offset")) {
335                 util::Timestamp offset;
336                 stringstream ss;
337                 ss << settings.value("offset").toString().toStdString();
338
339                 boost::archive::text_iarchive ia(ss);
340                 ia >> boost::serialization::make_nvp("offset", offset);
341
342                 set_offset(offset);
343         }
344
345         if (settings.contains("splitter_state"))
346                 splitter_->restoreState(settings.value("splitter_state").toByteArray());
347
348         for (shared_ptr<Signal> signal : signals_) {
349                 settings.beginGroup(signal->base()->internal_name());
350                 signal->restore_settings(settings);
351                 settings.endGroup();
352         }
353
354         if (settings.contains("v_offset")) {
355                 saved_v_offset_ = settings.value("v_offset").toInt();
356                 set_v_offset(saved_v_offset_);
357                 scroll_needs_defaults_ = false;
358                 // Note: see eventFilter() for additional information
359         }
360
361         settings_restored_ = true;
362         suppress_zoom_to_fit_after_acq_ = true;
363 }
364
365 vector< shared_ptr<TimeItem> > View::time_items() const
366 {
367         const vector<shared_ptr<Flag>> f(flags());
368         vector<shared_ptr<TimeItem>> items(f.begin(), f.end());
369         items.push_back(cursors_);
370         items.push_back(cursors_->first());
371         items.push_back(cursors_->second());
372
373         for (auto trigger_marker : trigger_markers_)
374                 items.push_back(trigger_marker);
375
376         return items;
377 }
378
379 double View::scale() const
380 {
381         return scale_;
382 }
383
384 void View::set_scale(double scale)
385 {
386         if (scale_ != scale) {
387                 scale_ = scale;
388                 scale_changed();
389         }
390 }
391
392 const Timestamp& View::offset() const
393 {
394         return offset_;
395 }
396
397 void View::set_offset(const pv::util::Timestamp& offset)
398 {
399         if (offset_ != offset) {
400                 offset_ = offset;
401                 offset_changed();
402         }
403 }
404
405 int View::owner_visual_v_offset() const
406 {
407         return -scrollarea_->verticalScrollBar()->sliderPosition();
408 }
409
410 void View::set_v_offset(int offset)
411 {
412         scrollarea_->verticalScrollBar()->setSliderPosition(offset);
413         header_->update();
414         viewport_->update();
415 }
416
417 unsigned int View::depth() const
418 {
419         return 0;
420 }
421
422 pv::util::SIPrefix View::tick_prefix() const
423 {
424         return tick_prefix_;
425 }
426
427 void View::set_tick_prefix(pv::util::SIPrefix tick_prefix)
428 {
429         if (tick_prefix_ != tick_prefix) {
430                 tick_prefix_ = tick_prefix;
431                 tick_prefix_changed();
432         }
433 }
434
435 unsigned int View::tick_precision() const
436 {
437         return tick_precision_;
438 }
439
440 void View::set_tick_precision(unsigned tick_precision)
441 {
442         if (tick_precision_ != tick_precision) {
443                 tick_precision_ = tick_precision;
444                 tick_precision_changed();
445         }
446 }
447
448 const pv::util::Timestamp& View::tick_period() const
449 {
450         return tick_period_;
451 }
452
453 void View::set_tick_period(const pv::util::Timestamp& tick_period)
454 {
455         if (tick_period_ != tick_period) {
456                 tick_period_ = tick_period;
457                 tick_period_changed();
458         }
459 }
460
461 TimeUnit View::time_unit() const
462 {
463         return time_unit_;
464 }
465
466 void View::set_time_unit(pv::util::TimeUnit time_unit)
467 {
468         if (time_unit_ != time_unit) {
469                 time_unit_ = time_unit;
470                 time_unit_changed();
471         }
472 }
473
474 void View::zoom(double steps)
475 {
476         zoom(steps, viewport_->width() / 2);
477 }
478
479 void View::zoom(double steps, int offset)
480 {
481         set_zoom(scale_ * pow(3.0 / 2.0, -steps), offset);
482 }
483
484 void View::zoom_fit(bool gui_state)
485 {
486         // Act as one-shot when stopped, toggle along with the GUI otherwise
487         if (session_.get_capture_state() == Session::Stopped) {
488                 always_zoom_to_fit_ = false;
489                 always_zoom_to_fit_changed(false);
490         } else {
491                 always_zoom_to_fit_ = gui_state;
492                 always_zoom_to_fit_changed(gui_state);
493         }
494
495         const pair<Timestamp, Timestamp> extents = get_time_extents();
496         const Timestamp delta = extents.second - extents.first;
497         if (delta < Timestamp("1e-12"))
498                 return;
499
500         assert(viewport_);
501         const int w = viewport_->width();
502         if (w <= 0)
503                 return;
504
505         const Timestamp scale = max(min(delta / w, MaxScale), MinScale);
506         set_scale_offset(scale.convert_to<double>(), extents.first);
507 }
508
509 void View::zoom_one_to_one()
510 {
511         using pv::data::SignalData;
512
513         // Make a set of all the visible data objects
514         set< shared_ptr<SignalData> > visible_data = get_visible_data();
515         if (visible_data.empty())
516                 return;
517
518         assert(viewport_);
519         const int w = viewport_->width();
520         if (w <= 0)
521                 return;
522
523         set_zoom(1.0 / session_.get_samplerate(), w / 2);
524 }
525
526 void View::set_scale_offset(double scale, const Timestamp& offset)
527 {
528         // Disable sticky scrolling / always zoom to fit when acquisition runs
529         // and user drags the viewport
530         if ((scale_ == scale) && (offset_ != offset) &&
531                         (session_.get_capture_state() == Session::Running)) {
532
533                 if (sticky_scrolling_) {
534                         sticky_scrolling_ = false;
535                         sticky_scrolling_changed(false);
536                 }
537
538                 if (always_zoom_to_fit_) {
539                         always_zoom_to_fit_ = false;
540                         always_zoom_to_fit_changed(false);
541                 }
542         }
543
544         set_scale(scale);
545         set_offset(offset);
546
547         calculate_tick_spacing();
548
549         update_scroll();
550         ruler_->update();
551         viewport_->update();
552 }
553
554 set< shared_ptr<SignalData> > View::get_visible_data() const
555 {
556         // Make a set of all the visible data objects
557         set< shared_ptr<SignalData> > visible_data;
558         for (const shared_ptr<Signal> sig : signals_)
559                 if (sig->enabled())
560                         visible_data.insert(sig->data());
561
562         return visible_data;
563 }
564
565 pair<Timestamp, Timestamp> View::get_time_extents() const
566 {
567         boost::optional<Timestamp> left_time, right_time;
568         const set< shared_ptr<SignalData> > visible_data = get_visible_data();
569         for (const shared_ptr<SignalData> d : visible_data) {
570                 const vector< shared_ptr<Segment> > segments = d->segments();
571                 for (const shared_ptr<Segment> &s : segments) {
572                         double samplerate = s->samplerate();
573                         samplerate = (samplerate <= 0.0) ? 1.0 : samplerate;
574
575                         const Timestamp start_time = s->start_time();
576                         left_time = left_time ?
577                                 min(*left_time, start_time) :
578                                                 start_time;
579                         right_time = right_time ?
580                                 max(*right_time, start_time + d->max_sample_count() / samplerate) :
581                                                  start_time + d->max_sample_count() / samplerate;
582                 }
583         }
584
585         if (!left_time || !right_time)
586                 return make_pair(0, 0);
587
588         assert(*left_time < *right_time);
589         return make_pair(*left_time, *right_time);
590 }
591
592 void View::enable_show_sampling_points(bool state)
593 {
594         (void)state;
595
596         viewport_->update();
597 }
598
599 void View::enable_show_analog_minor_grid(bool state)
600 {
601         (void)state;
602
603         viewport_->update();
604 }
605
606 void View::enable_coloured_bg(bool state)
607 {
608         coloured_bg_ = state;
609         viewport_->update();
610 }
611
612 bool View::coloured_bg() const
613 {
614         return coloured_bg_;
615 }
616
617 bool View::cursors_shown() const
618 {
619         return show_cursors_;
620 }
621
622 void View::show_cursors(bool show)
623 {
624         show_cursors_ = show;
625         ruler_->update();
626         viewport_->update();
627 }
628
629 void View::centre_cursors()
630 {
631         const double time_width = scale_ * viewport_->width();
632         cursors_->first()->set_time(offset_ + time_width * 0.4);
633         cursors_->second()->set_time(offset_ + time_width * 0.6);
634         ruler_->update();
635         viewport_->update();
636 }
637
638 shared_ptr<CursorPair> View::cursors() const
639 {
640         return cursors_;
641 }
642
643 void View::add_flag(const Timestamp& time)
644 {
645         flags_.push_back(make_shared<Flag>(*this, time,
646                 QString("%1").arg(next_flag_text_)));
647
648         next_flag_text_ = (next_flag_text_ >= 'Z') ? 'A' :
649                 (next_flag_text_ + 1);
650
651         time_item_appearance_changed(true, true);
652 }
653
654 void View::remove_flag(shared_ptr<Flag> flag)
655 {
656         flags_.remove(flag);
657         time_item_appearance_changed(true, true);
658 }
659
660 vector< shared_ptr<Flag> > View::flags() const
661 {
662         vector< shared_ptr<Flag> > flags(flags_.begin(), flags_.end());
663         stable_sort(flags.begin(), flags.end(),
664                 [](const shared_ptr<Flag> &a, const shared_ptr<Flag> &b) {
665                         return a->time() < b->time();
666                 });
667
668         return flags;
669 }
670
671 const QPoint& View::hover_point() const
672 {
673         return hover_point_;
674 }
675
676 void View::restack_all_trace_tree_items()
677 {
678         // Make a list of owners that is sorted from deepest first
679         const vector<shared_ptr<TraceTreeItem>> items(
680                 list_by_type<TraceTreeItem>());
681         set< TraceTreeItemOwner* > owners;
682         for (const auto &r : items)
683                 owners.insert(r->owner());
684         vector< TraceTreeItemOwner* > sorted_owners(owners.begin(), owners.end());
685         sort(sorted_owners.begin(), sorted_owners.end(),
686                 [](const TraceTreeItemOwner* a, const TraceTreeItemOwner *b) {
687                         return a->depth() > b->depth(); });
688
689         // Restack the items recursively
690         for (auto &o : sorted_owners)
691                 o->restack_items();
692
693         // Animate the items to their destination
694         for (const auto &i : items)
695                 i->animate_to_layout_v_offset();
696 }
697
698 void View::trigger_event(util::Timestamp location)
699 {
700         trigger_markers_.push_back(make_shared<TriggerMarker>(*this, location));
701 }
702
703 void View::get_scroll_layout(double &length, Timestamp &offset) const
704 {
705         const pair<Timestamp, Timestamp> extents = get_time_extents();
706         length = ((extents.second - extents.first) / scale_).convert_to<double>();
707         offset = offset_ / scale_;
708 }
709
710 void View::set_zoom(double scale, int offset)
711 {
712         // Reset the "always zoom to fit" feature as the user changed the zoom
713         always_zoom_to_fit_ = false;
714         always_zoom_to_fit_changed(false);
715
716         const Timestamp cursor_offset = offset_ + scale_ * offset;
717         const Timestamp new_scale = max(min(Timestamp(scale), MaxScale), MinScale);
718         const Timestamp new_offset = cursor_offset - new_scale * offset;
719         set_scale_offset(new_scale.convert_to<double>(), new_offset);
720 }
721
722 void View::calculate_tick_spacing()
723 {
724         const double SpacingIncrement = 10.0f;
725         const double MinValueSpacing = 40.0f;
726
727         // Figure out the highest numeric value visible on a label
728         const QSize areaSize = viewport_->size();
729         const Timestamp max_time = max(fabs(offset_),
730                 fabs(offset_ + scale_ * areaSize.width()));
731
732         double min_width = SpacingIncrement;
733         double label_width, tick_period_width;
734
735         QFontMetrics m(QApplication::font());
736
737         // Copies of the member variables with the same name, used in the calculation
738         // and written back afterwards, so that we don't emit signals all the time
739         // during the calculation.
740         pv::util::Timestamp tick_period = tick_period_;
741         pv::util::SIPrefix tick_prefix = tick_prefix_;
742         unsigned tick_precision = tick_precision_;
743
744         do {
745                 const double min_period = scale_ * min_width;
746
747                 const int order = (int)floorf(log10f(min_period));
748                 const pv::util::Timestamp order_decimal =
749                         pow(pv::util::Timestamp(10), order);
750
751                 // Allow for a margin of error so that a scale unit of 1 can be used.
752                 // Otherwise, for a SU of 1 the tick period will almost always be below
753                 // the min_period by a small amount - and thus skipped in favor of 2.
754                 // Note: margin assumes that SU[0] and SU[1] contain the smallest values
755                 double tp_margin = (ScaleUnits[0] + ScaleUnits[1]) / 2.0;
756                 double tp_with_margin;
757                 unsigned int unit = 0;
758
759                 do {
760                         tp_with_margin = order_decimal.convert_to<double>() *
761                                 (ScaleUnits[unit++] + tp_margin);
762                 } while (tp_with_margin < min_period && unit < countof(ScaleUnits));
763
764                 tick_period = order_decimal * ScaleUnits[unit - 1];
765                 tick_prefix = static_cast<pv::util::SIPrefix>(
766                         (order - pv::util::exponent(pv::util::SIPrefix::yocto)) / 3);
767
768                 // Precision is the number of fractional digits required, not
769                 // taking the prefix into account (and it must never be negative)
770                 tick_precision = max(ceil(log10(1 / tick_period)).convert_to<int>(), 0);
771
772                 tick_period_width = (tick_period / scale_).convert_to<double>();
773
774                 const QString label_text = Ruler::format_time_with_distance(
775                         tick_period, max_time, tick_prefix, time_unit_, tick_precision);
776
777                 label_width = m.boundingRect(0, 0, INT_MAX, INT_MAX,
778                         Qt::AlignLeft | Qt::AlignTop, label_text).width() +
779                                 MinValueSpacing;
780
781                 min_width += SpacingIncrement;
782         } while (tick_period_width < label_width);
783
784         set_tick_period(tick_period);
785         set_tick_prefix(tick_prefix);
786         set_tick_precision(tick_precision);
787 }
788
789 void View::adjust_top_margin()
790 {
791         assert(viewport_);
792
793         const QSize areaSize = viewport_->size();
794
795         const pair<int, int> extents = v_extents();
796         const int top_margin = owner_visual_v_offset() + extents.first;
797         const int trace_bottom = owner_visual_v_offset() + extents.first + extents.second;
798
799         // Do we have empty space at the top while the last trace goes out of screen?
800         if ((top_margin > 0) && (trace_bottom > areaSize.height())) {
801                 const int trace_height = extents.second - extents.first;
802
803                 // Center everything vertically if there is enough space
804                 if (areaSize.height() >= trace_height)
805                         set_v_offset(extents.first -
806                                 ((areaSize.height() - trace_height) / 2));
807                 else
808                         // Remove the top margin to make as many traces fit on screen as possible
809                         set_v_offset(extents.first);
810         }
811 }
812
813 void View::update_scroll()
814 {
815         assert(viewport_);
816         QScrollBar *hscrollbar = scrollarea_->horizontalScrollBar();
817         QScrollBar *vscrollbar = scrollarea_->verticalScrollBar();
818
819         const QSize areaSize = viewport_->size();
820
821         // Set the horizontal scroll bar
822         double length = 0;
823         Timestamp offset;
824         get_scroll_layout(length, offset);
825         length = max(length - areaSize.width(), 0.0);
826
827         int major_tick_distance = (tick_period_ / scale_).convert_to<int>();
828
829         hscrollbar->setPageStep(areaSize.width() / 2);
830         hscrollbar->setSingleStep(major_tick_distance);
831
832         updating_scroll_ = true;
833
834         if (length < MaxScrollValue) {
835                 hscrollbar->setRange(0, length);
836                 hscrollbar->setSliderPosition(offset.convert_to<double>());
837         } else {
838                 hscrollbar->setRange(0, MaxScrollValue);
839                 hscrollbar->setSliderPosition(
840                         (offset_ * MaxScrollValue / (scale_ * length)).convert_to<double>());
841         }
842
843         updating_scroll_ = false;
844
845         // Set the vertical scrollbar
846         vscrollbar->setPageStep(areaSize.height());
847         vscrollbar->setSingleStep(areaSize.height() / 8);
848
849         const pair<int, int> extents = v_extents();
850
851         // Don't change the scrollbar range if there are no traces
852         if (extents.first != extents.second)
853                 vscrollbar->setRange(extents.first - areaSize.height(),
854                         extents.second);
855
856         if (scroll_needs_defaults_)
857                 set_scroll_default();
858 }
859
860 void View::reset_scroll()
861 {
862         scrollarea_->verticalScrollBar()->setRange(0, 0);
863 }
864
865 void View::set_scroll_default()
866 {
867         assert(viewport_);
868
869         const QSize areaSize = viewport_->size();
870
871         const pair<int, int> extents = v_extents();
872         const int trace_height = extents.second - extents.first;
873
874         // Do all traces fit in the view?
875         if (areaSize.height() >= trace_height)
876                 // Center all traces vertically
877                 set_v_offset(extents.first -
878                         ((areaSize.height() - trace_height) / 2));
879         else
880                 // Put the first trace at the top, letting the bottom ones overflow
881                 set_v_offset(extents.first);
882 }
883
884 bool View::header_was_shrunk() const
885 {
886         const int header_pane_width = splitter_->sizes().front();
887         const int header_width = header_->extended_size_hint().width();
888
889         // Allow for a slight margin of error so that we also accept
890         // slight differences when e.g. a label name change increased
891         // the overall width
892         return (header_pane_width < (header_width - 10));
893 }
894
895 void View::expand_header_to_fit()
896 {
897         int splitter_area_width = 0;
898         for (int w : splitter_->sizes())
899                 splitter_area_width += w;
900
901         // Make sure the header has enough horizontal space to show all labels fully
902         QList<int> pane_sizes;
903         pane_sizes.push_back(header_->extended_size_hint().width());
904         pane_sizes.push_back(splitter_area_width - header_->extended_size_hint().width());
905         splitter_->setSizes(pane_sizes);
906 }
907
908 void View::update_layout()
909 {
910         update_scroll();
911 }
912
913 TraceTreeItemOwner* View::find_prevalent_trace_group(
914         const shared_ptr<sigrok::ChannelGroup> &group,
915         const unordered_map<shared_ptr<data::SignalBase>, shared_ptr<Signal> >
916                 &signal_map)
917 {
918         assert(group);
919
920         unordered_set<TraceTreeItemOwner*> owners;
921         vector<TraceTreeItemOwner*> owner_list;
922
923         // Make a set and a list of all the owners
924         for (const auto &channel : group->channels()) {
925                 for (auto entry : signal_map) {
926                         if (entry.first->channel() == channel) {
927                                 TraceTreeItemOwner *const o = (entry.second)->owner();
928                                 owner_list.push_back(o);
929                                 owners.insert(o);
930                         }
931                 }
932         }
933
934         // Iterate through the list of owners, and find the most prevalent
935         size_t max_prevalence = 0;
936         TraceTreeItemOwner *prevalent_owner = nullptr;
937         for (TraceTreeItemOwner *owner : owners) {
938                 const size_t prevalence = count_if(
939                         owner_list.begin(), owner_list.end(),
940                         [&](TraceTreeItemOwner *o) { return o == owner; });
941                 if (prevalence > max_prevalence) {
942                         max_prevalence = prevalence;
943                         prevalent_owner = owner;
944                 }
945         }
946
947         return prevalent_owner;
948 }
949
950 vector< shared_ptr<Trace> > View::extract_new_traces_for_channels(
951         const vector< shared_ptr<sigrok::Channel> > &channels,
952         const unordered_map<shared_ptr<data::SignalBase>, shared_ptr<Signal> >
953                 &signal_map,
954         set< shared_ptr<Trace> > &add_list)
955 {
956         vector< shared_ptr<Trace> > filtered_traces;
957
958         for (const auto &channel : channels) {
959                 for (auto entry : signal_map) {
960                         if (entry.first->channel() == channel) {
961                                 shared_ptr<Trace> trace = entry.second;
962                                 const auto list_iter = add_list.find(trace);
963                                 if (list_iter == add_list.end())
964                                         continue;
965
966                                 filtered_traces.push_back(trace);
967                                 add_list.erase(list_iter);
968                         }
969                 }
970         }
971
972         return filtered_traces;
973 }
974
975 void View::determine_time_unit()
976 {
977         // Check whether we know the sample rate and hence can use time as the unit
978         if (time_unit_ == util::TimeUnit::Samples) {
979                 // Check all signals but...
980                 for (const shared_ptr<Signal> signal : signals_) {
981                         const shared_ptr<SignalData> data = signal->data();
982
983                         // ...only check first segment of each
984                         const vector< shared_ptr<Segment> > segments = data->segments();
985                         if (!segments.empty())
986                                 if (segments[0]->samplerate()) {
987                                         set_time_unit(util::TimeUnit::Time);
988                                         break;
989                                 }
990                 }
991         }
992 }
993
994 bool View::eventFilter(QObject *object, QEvent *event)
995 {
996         const QEvent::Type type = event->type();
997         if (type == QEvent::MouseMove) {
998
999                 const QMouseEvent *const mouse_event = (QMouseEvent*)event;
1000                 if (object == viewport_)
1001                         hover_point_ = mouse_event->pos();
1002                 else if (object == ruler_)
1003                         hover_point_ = QPoint(mouse_event->x(), 0);
1004                 else if (object == header_)
1005                         hover_point_ = QPoint(0, mouse_event->y());
1006                 else
1007                         hover_point_ = QPoint(-1, -1);
1008
1009                 hover_point_changed();
1010
1011         } else if (type == QEvent::Leave) {
1012                 hover_point_ = QPoint(-1, -1);
1013                 hover_point_changed();
1014         } else if (type == QEvent::Show) {
1015
1016                 // This is somewhat of a hack, unfortunately. We cannot use
1017                 // set_v_offset() from within restore_settings() as the view
1018                 // at that point is neither visible nor properly sized.
1019                 // This is the least intrusive workaround I could come up
1020                 // with: set the vertical offset (or scroll defaults) when
1021                 // the view is shown, which happens after all widgets were
1022                 // resized to their final sizes.
1023                 update_layout();
1024
1025                 if (!settings_restored_)
1026                         expand_header_to_fit();
1027
1028                 if (scroll_needs_defaults_) {
1029                         set_scroll_default();
1030                         scroll_needs_defaults_ = false;
1031                 }
1032
1033                 if (saved_v_offset_) {
1034                         set_v_offset(saved_v_offset_);
1035                         saved_v_offset_ = 0;
1036                 }
1037         }
1038
1039         return QObject::eventFilter(object, event);
1040 }
1041
1042 void View::resizeEvent(QResizeEvent* event)
1043 {
1044         // Only adjust the top margin if we shrunk vertically
1045         if (event->size().height() < event->oldSize().height())
1046                 adjust_top_margin();
1047
1048         update_layout();
1049 }
1050
1051 void View::row_item_appearance_changed(bool label, bool content)
1052 {
1053         if (label)
1054                 header_->update();
1055         if (content)
1056                 viewport_->update();
1057 }
1058
1059 void View::time_item_appearance_changed(bool label, bool content)
1060 {
1061         if (label) {
1062                 ruler_->update();
1063
1064                 // Make sure the header pane width is updated, too
1065                 update_layout();
1066         }
1067
1068         if (content)
1069                 viewport_->update();
1070 }
1071
1072 void View::extents_changed(bool horz, bool vert)
1073 {
1074         sticky_events_ |=
1075                 (horz ? TraceTreeItemHExtentsChanged : 0) |
1076                 (vert ? TraceTreeItemVExtentsChanged : 0);
1077
1078         lazy_event_handler_.start();
1079 }
1080
1081 void View::on_splitter_moved()
1082 {
1083         // Setting the maximum width of the header widget doesn't work as
1084         // expected because the splitter would allow the user to make the
1085         // pane wider than that, creating empty space as a result.
1086         // To make this work, we stricly enforce the maximum width by
1087         // expanding the header unless the user shrunk it on purpose.
1088         // As we're then setting the width of the header pane, we set the
1089         // splitter to the maximum allowed position.
1090         if (!header_was_shrunk())
1091                 expand_header_to_fit();
1092 }
1093
1094 void View::h_scroll_value_changed(int value)
1095 {
1096         if (updating_scroll_)
1097                 return;
1098
1099         // Disable sticky scrolling when user moves the horizontal scroll bar
1100         // during a running acquisition
1101         if (sticky_scrolling_ && (session_.get_capture_state() == Session::Running)) {
1102                 sticky_scrolling_ = false;
1103                 sticky_scrolling_changed(false);
1104         }
1105
1106         const int range = scrollarea_->horizontalScrollBar()->maximum();
1107         if (range < MaxScrollValue)
1108                 set_offset(scale_ * value);
1109         else {
1110                 double length = 0;
1111                 Timestamp offset;
1112                 get_scroll_layout(length, offset);
1113                 set_offset(scale_ * length * value / MaxScrollValue);
1114         }
1115
1116         ruler_->update();
1117         viewport_->update();
1118 }
1119
1120 void View::v_scroll_value_changed()
1121 {
1122         header_->update();
1123         viewport_->update();
1124 }
1125
1126 void View::signals_changed()
1127 {
1128         using sigrok::Channel;
1129
1130         vector< shared_ptr<Channel> > channels;
1131         shared_ptr<sigrok::Device> sr_dev;
1132
1133         // Do we need to set the vertical scrollbar to its default position later?
1134         // We do if there are no traces, i.e. the scroll bar has no range set
1135         bool reset_scrollbar =
1136                 (scrollarea_->verticalScrollBar()->minimum() ==
1137                         scrollarea_->verticalScrollBar()->maximum());
1138
1139         if (!session_.device()) {
1140                 reset_scroll();
1141                 signals_.clear();
1142         } else {
1143                 sr_dev = session_.device()->device();
1144                 assert(sr_dev);
1145                 channels = sr_dev->channels();
1146         }
1147
1148         vector< shared_ptr<TraceTreeItem> > new_top_level_items;
1149
1150         // Make a list of traces that are being added, and a list of traces
1151         // that are being removed
1152         const vector<shared_ptr<Trace>> prev_trace_list = list_by_type<Trace>();
1153         const set<shared_ptr<Trace>> prev_traces(
1154                 prev_trace_list.begin(), prev_trace_list.end());
1155
1156         set< shared_ptr<Trace> > traces(signals_.begin(), signals_.end());
1157
1158 #ifdef ENABLE_DECODE
1159         traces.insert(decode_traces_.begin(), decode_traces_.end());
1160 #endif
1161
1162         set< shared_ptr<Trace> > add_traces;
1163         set_difference(traces.begin(), traces.end(),
1164                 prev_traces.begin(), prev_traces.end(),
1165                 inserter(add_traces, add_traces.begin()));
1166
1167         set< shared_ptr<Trace> > remove_traces;
1168         set_difference(prev_traces.begin(), prev_traces.end(),
1169                 traces.begin(), traces.end(),
1170                 inserter(remove_traces, remove_traces.begin()));
1171
1172         // Make a look-up table of sigrok Channels to pulseview Signals
1173         unordered_map<shared_ptr<data::SignalBase>, shared_ptr<Signal> >
1174                 signal_map;
1175         for (const shared_ptr<Signal> &sig : signals_)
1176                 signal_map[sig->base()] = sig;
1177
1178         // Populate channel groups
1179         if (sr_dev)
1180                 for (auto entry : sr_dev->channel_groups()) {
1181                         const shared_ptr<sigrok::ChannelGroup> &group = entry.second;
1182
1183                         if (group->channels().size() <= 1)
1184                                 continue;
1185
1186                         // Find best trace group to add to
1187                         TraceTreeItemOwner *owner = find_prevalent_trace_group(
1188                                 group, signal_map);
1189
1190                         // If there is no trace group, create one
1191                         shared_ptr<TraceGroup> new_trace_group;
1192                         if (!owner) {
1193                                 new_trace_group.reset(new TraceGroup());
1194                                 owner = new_trace_group.get();
1195                         }
1196
1197                         // Extract traces for the trace group, removing them from
1198                         // the add list
1199                         const vector< shared_ptr<Trace> > new_traces_in_group =
1200                                 extract_new_traces_for_channels(group->channels(),
1201                                         signal_map, add_traces);
1202
1203                         // Add the traces to the group
1204                         const pair<int, int> prev_v_extents = owner->v_extents();
1205                         int offset = prev_v_extents.second - prev_v_extents.first;
1206                         for (shared_ptr<Trace> trace : new_traces_in_group) {
1207                                 assert(trace);
1208                                 owner->add_child_item(trace);
1209
1210                                 const pair<int, int> extents = trace->v_extents();
1211                                 if (trace->enabled())
1212                                         offset += -extents.first;
1213                                 trace->force_to_v_offset(offset);
1214                                 if (trace->enabled())
1215                                         offset += extents.second;
1216                         }
1217
1218                         if (new_trace_group) {
1219                                 // Assign proper vertical offsets to each channel in the group
1220                                 new_trace_group->restack_items();
1221
1222                                 // If this is a new group, enqueue it in the new top level
1223                                 // items list
1224                                 if (!new_traces_in_group.empty())
1225                                         new_top_level_items.push_back(new_trace_group);
1226                         }
1227                 }
1228
1229         // Enqueue the remaining logic channels in a group
1230         vector< shared_ptr<Channel> > logic_channels;
1231         copy_if(channels.begin(), channels.end(), back_inserter(logic_channels),
1232                 [](const shared_ptr<Channel>& c) {
1233                         return c->type() == sigrok::ChannelType::LOGIC; });
1234
1235         const vector< shared_ptr<Trace> > non_grouped_logic_signals =
1236                 extract_new_traces_for_channels(logic_channels, signal_map, add_traces);
1237
1238         if (non_grouped_logic_signals.size() > 0) {
1239                 const shared_ptr<TraceGroup> non_grouped_trace_group(
1240                         make_shared<TraceGroup>());
1241                 for (shared_ptr<Trace> trace : non_grouped_logic_signals)
1242                         non_grouped_trace_group->add_child_item(trace);
1243
1244                 non_grouped_trace_group->restack_items();
1245                 new_top_level_items.push_back(non_grouped_trace_group);
1246         }
1247
1248         // Enqueue the remaining channels as free ungrouped traces
1249         const vector< shared_ptr<Trace> > new_top_level_signals =
1250                 extract_new_traces_for_channels(channels, signal_map, add_traces);
1251         new_top_level_items.insert(new_top_level_items.end(),
1252                 new_top_level_signals.begin(), new_top_level_signals.end());
1253
1254         // Enqueue any remaining traces i.e. decode traces
1255         new_top_level_items.insert(new_top_level_items.end(),
1256                 add_traces.begin(), add_traces.end());
1257
1258         // Remove any removed traces
1259         for (shared_ptr<Trace> trace : remove_traces) {
1260                 TraceTreeItemOwner *const owner = trace->owner();
1261                 assert(owner);
1262                 owner->remove_child_item(trace);
1263         }
1264
1265         // Remove any empty trace groups
1266         for (shared_ptr<TraceGroup> group : list_by_type<TraceGroup>())
1267                 if (group->child_items().size() == 0) {
1268                         remove_child_item(group);
1269                         group.reset();
1270                 }
1271
1272         // Add and position the pending top levels items
1273         int offset = v_extents().second;
1274         for (auto item : new_top_level_items) {
1275                 add_child_item(item);
1276
1277                 // Position the item after the last item or at the top if there is none
1278                 const pair<int, int> extents = item->v_extents();
1279
1280                 if (item->enabled())
1281                         offset += -extents.first;
1282
1283                 item->force_to_v_offset(offset);
1284
1285                 if (item->enabled())
1286                         offset += extents.second;
1287         }
1288
1289
1290         if (!new_top_level_items.empty())
1291                 // Expand the header pane because the header should become fully
1292                 // visible when new signals are added
1293                 expand_header_to_fit();
1294
1295         update_layout();
1296
1297         header_->update();
1298         viewport_->update();
1299
1300         if (reset_scrollbar)
1301                 set_scroll_default();
1302 }
1303
1304 void View::capture_state_updated(int state)
1305 {
1306         GlobalSettings settings;
1307
1308         if (state == Session::Running) {
1309                 set_time_unit(util::TimeUnit::Samples);
1310
1311                 trigger_markers_.clear();
1312
1313                 scale_at_acq_start_ = scale_;
1314                 offset_at_acq_start_ = offset_;
1315
1316                 // Activate "always zoom to fit" if the setting is enabled and we're
1317                 // the main view of this session (other trace views may be used for
1318                 // zooming and we don't want to mess them up)
1319                 bool state = settings.value(GlobalSettings::Key_View_AlwaysZoomToFit).toBool();
1320                 if (is_main_view_ && state) {
1321                         always_zoom_to_fit_ = true;
1322                         always_zoom_to_fit_changed(always_zoom_to_fit_);
1323                 }
1324
1325                 // Enable sticky scrolling if the setting is enabled
1326                 sticky_scrolling_ = settings.value(GlobalSettings::Key_View_StickyScrolling).toBool();
1327         }
1328
1329         if (state == Session::Stopped) {
1330                 // After acquisition has stopped we need to re-calculate the ticks once
1331                 // as it's otherwise done when the user pans or zooms, which is too late
1332                 calculate_tick_spacing();
1333
1334                 // Reset "always zoom to fit", the acquisition has stopped
1335                 if (always_zoom_to_fit_) {
1336                         // Perform a final zoom-to-fit before disabling
1337                         zoom_fit(always_zoom_to_fit_);
1338                         always_zoom_to_fit_ = false;
1339                         always_zoom_to_fit_changed(always_zoom_to_fit_);
1340                 }
1341
1342                 bool zoom_to_fit_after_acq =
1343                         settings.value(GlobalSettings::Key_View_ZoomToFitAfterAcq).toBool();
1344
1345                 // Only perform zoom-to-fit if the user hasn't altered the viewport and
1346                 // we didn't restore settings in the meanwhile
1347                 if (zoom_to_fit_after_acq &&
1348                         !suppress_zoom_to_fit_after_acq_ &&
1349                         (scale_ == scale_at_acq_start_) &&
1350                         (offset_ == offset_at_acq_start_))
1351                         zoom_fit(false);  // We're stopped, so the GUI state doesn't matter
1352
1353                 suppress_zoom_to_fit_after_acq_ = false;
1354         }
1355 }
1356
1357 void View::perform_delayed_view_update()
1358 {
1359         if (always_zoom_to_fit_) {
1360                 zoom_fit(true);
1361         } else if (sticky_scrolling_) {
1362                 // Make right side of the view sticky
1363                 double length = 0;
1364                 Timestamp offset;
1365                 get_scroll_layout(length, offset);
1366
1367                 const QSize areaSize = viewport_->size();
1368                 length = max(length - areaSize.width(), 0.0);
1369
1370                 set_offset(scale_ * length);
1371         }
1372
1373         determine_time_unit();
1374         update_scroll();
1375         ruler_->update();
1376         viewport_->update();
1377 }
1378
1379 void View::process_sticky_events()
1380 {
1381         if (sticky_events_ & TraceTreeItemHExtentsChanged)
1382                 update_layout();
1383         if (sticky_events_ & TraceTreeItemVExtentsChanged) {
1384                 restack_all_trace_tree_items();
1385                 update_scroll();
1386         }
1387
1388         // Clear the sticky events
1389         sticky_events_ = 0;
1390 }
1391
1392 void View::on_hover_point_changed()
1393 {
1394         const vector<shared_ptr<TraceTreeItem>> trace_tree_items(
1395                 list_by_type<TraceTreeItem>());
1396         for (shared_ptr<TraceTreeItem> r : trace_tree_items)
1397                 r->hover_point_changed();
1398 }
1399
1400 } // namespace trace
1401 } // namespace views
1402 } // namespace pv