PulseView  unreleased development snapshot
A Qt-based sigrok GUI
view.cpp
Go to the documentation of this file.
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 
33 #include <QApplication>
34 #include <QDebug>
35 #include <QEvent>
36 #include <QFontMetrics>
37 #include <QMenu>
38 #include <QMouseEvent>
39 #include <QScrollBar>
40 #include <QVBoxLayout>
41 
42 #include <libsigrokcxx/libsigrokcxx.hpp>
43 
44 #include "view.hpp"
45 
46 #include "pv/globalsettings.hpp"
47 #include "pv/metadata_obj.hpp"
48 #include "pv/session.hpp"
49 #include "pv/util.hpp"
50 #include "pv/data/logic.hpp"
51 #include "pv/data/logicsegment.hpp"
52 #include "pv/data/signalbase.hpp"
53 #include "pv/devices/device.hpp"
58 #include "pv/views/trace/ruler.hpp"
63 
64 #ifdef ENABLE_DECODE
66 #endif
67 
70 using pv::data::Segment;
71 using pv::util::TimeUnit;
73 
74 using std::back_inserter;
75 using std::copy_if;
76 using std::count_if;
77 using std::inserter;
78 using std::lock_guard;
79 using std::max;
80 using std::make_pair;
81 using std::make_shared;
82 using std::min;
83 using std::numeric_limits;
84 using std::pair;
85 using std::set;
86 using std::set_difference;
87 using std::shared_ptr;
88 using std::vector;
89 
90 namespace pv {
91 namespace views {
92 namespace trace {
93 
94 const Timestamp View::MaxScale("1e9");
95 const Timestamp View::MinScale("1e-14");
96 
97 const int View::MaxScrollValue = INT_MAX / 2;
98 
99 /* Area at the top and bottom of the view that can't be scrolled out of sight */
100 const int View::ViewScrollMargin = 50;
101 
102 const int View::ScaleUnits[3] = {1, 2, 5};
103 
105  QAbstractScrollArea(parent)
106 {
107 }
108 
110 {
111  switch (event->type()) {
112  case QEvent::Paint:
113  case QEvent::MouseButtonPress:
114  case QEvent::MouseButtonRelease:
115  case QEvent::MouseButtonDblClick:
116  case QEvent::MouseMove:
117  case QEvent::Wheel:
118  case QEvent::TouchBegin:
119  case QEvent::TouchUpdate:
120  case QEvent::TouchEnd:
121  return false;
122  default:
123  return QAbstractScrollArea::viewportEvent(event);
124  }
125 }
126 
127 View::View(Session &session, bool is_main_view, QMainWindow *parent) :
128  ViewBase(session, is_main_view, parent),
129 
130  // Note: Place defaults in View::reset_view_state(), not here
131  splitter_(new QSplitter()),
132  header_was_shrunk_(false), // The splitter remains unchanged after a reset, so this goes here
133  sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui()
134  scroll_needs_defaults_(true)
135 {
136  QVBoxLayout *root_layout = new QVBoxLayout(this);
137  root_layout->setContentsMargins(0, 0, 0, 0);
138  root_layout->addWidget(splitter_);
139 
140  viewport_ = new Viewport(*this);
142  scrollarea_->setViewport(viewport_);
143  scrollarea_->setFrameShape(QFrame::NoFrame);
144 
145  ruler_ = new Ruler(*this);
146 
147  header_ = new Header(*this);
148  header_->setMinimumWidth(10); // So that the arrow tips show at least
149 
150  // We put the header into a simple layout so that we can add the top margin,
151  // allowing us to make it line up with the bottom of the ruler
152  QWidget *header_container = new QWidget();
153  header_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
154  QVBoxLayout *header_layout = new QVBoxLayout(header_container);
155  header_layout->setContentsMargins(0, ruler_->sizeHint().height(), 0, 0);
156  header_layout->addWidget(header_);
157 
158  // To let the ruler and scrollarea be on the same split pane, we need a layout
159  QWidget *trace_container = new QWidget();
160  trace_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
161  QVBoxLayout *trace_layout = new QVBoxLayout(trace_container);
162  trace_layout->setSpacing(0); // We don't want space between the ruler and scrollarea
163  trace_layout->setContentsMargins(0, 0, 0, 0);
164  trace_layout->addWidget(ruler_);
165  trace_layout->addWidget(scrollarea_);
166 
167  splitter_->addWidget(header_container);
168  splitter_->addWidget(trace_container);
169  splitter_->setHandleWidth(1); // Don't show a visible rubber band
170  splitter_->setCollapsible(0, false); // Prevent the header from collapsing
171  splitter_->setCollapsible(1, false); // Prevent the traces from collapsing
172  splitter_->setStretchFactor(0, 0); // Prevent the panes from being resized
173  splitter_->setStretchFactor(1, 1); // when the entire view is resized
174  splitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
175 
176  viewport_->installEventFilter(this);
177  ruler_->installEventFilter(this);
178  header_->installEventFilter(this);
179 
180  // Set up settings and event handlers
181  GlobalSettings settings;
182  colored_bg_ = settings.value(GlobalSettings::Key_View_ColoredBG).toBool();
183  snap_distance_ = settings.value(GlobalSettings::Key_View_SnapDistance).toInt();
184 
186 
187  // Set up metadata objects and event handlers
188  if (is_main_view) {
191  }
192 
193  // Set up UI event handlers
194  connect(scrollarea_->horizontalScrollBar(), SIGNAL(valueChanged(int)),
195  this, SLOT(h_scroll_value_changed(int)));
196  connect(scrollarea_->verticalScrollBar(), SIGNAL(valueChanged(int)),
197  this, SLOT(v_scroll_value_changed()));
198 
199  connect(header_, SIGNAL(selection_changed()),
200  ruler_, SLOT(clear_selection()));
201  connect(ruler_, SIGNAL(selection_changed()),
202  header_, SLOT(clear_selection()));
203 
204  connect(header_, SIGNAL(selection_changed()),
205  this, SIGNAL(selection_changed()));
206  connect(ruler_, SIGNAL(selection_changed()),
207  this, SIGNAL(selection_changed()));
208 
209  connect(splitter_, SIGNAL(splitterMoved(int, int)),
210  this, SLOT(on_splitter_moved()));
211 
212  connect(&lazy_event_handler_, SIGNAL(timeout()),
213  this, SLOT(process_sticky_events()));
214  lazy_event_handler_.setSingleShot(true);
216 
217  // Set up local keyboard shortcuts
218  zoom_in_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Plus), this,
219  SLOT(on_zoom_in_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
220  zoom_in_shortcut_->setAutoRepeat(false);
221 
222  zoom_out_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Minus), this,
223  SLOT(on_zoom_out_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
224  zoom_out_shortcut_->setAutoRepeat(false);
225 
226  zoom_in_shortcut_2_ = new QShortcut(QKeySequence(Qt::Key_Up), this,
227  SLOT(on_zoom_in_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
228  zoom_out_shortcut_2_ = new QShortcut(QKeySequence(Qt::Key_Down), this,
229  SLOT(on_zoom_out_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
230 
231  home_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Home), this,
232  SLOT(on_scroll_to_start_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
233  home_shortcut_->setAutoRepeat(false);
234 
235  end_shortcut_ = new QShortcut(QKeySequence(Qt::Key_End), this,
236  SLOT(on_scroll_to_end_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
237  end_shortcut_->setAutoRepeat(false);
238 
239  grab_ruler_left_shortcut_ = new QShortcut(QKeySequence(Qt::Key_1), this,
240  nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
241  connect(grab_ruler_left_shortcut_, &QShortcut::activated,
242  this, [=]{on_grab_ruler(1);});
243  grab_ruler_left_shortcut_->setAutoRepeat(false);
244 
245  grab_ruler_right_shortcut_ = new QShortcut(QKeySequence(Qt::Key_2), this,
246  nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
247  connect(grab_ruler_right_shortcut_, &QShortcut::activated,
248  this, [=]{on_grab_ruler(2);});
249  grab_ruler_right_shortcut_->setAutoRepeat(false);
250 
251  cancel_grab_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this,
252  nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
253  connect(cancel_grab_shortcut_, &QShortcut::activated,
254  this, [=]{grabbed_widget_ = nullptr;});
255  cancel_grab_shortcut_->setAutoRepeat(false);
256 
257  // Trigger the initial event manually. The default device has signals
258  // which were created before this object came into being
259  signals_changed();
260 
261  // Make sure the transparent widgets are on the top
262  ruler_->raise();
263  header_->raise();
264 
266 }
267 
269 {
271 }
272 
274 {
275  return ViewTypeTrace;
276 }
277 
279 {
281 
283  segment_selectable_ = false;
284  scale_ = 1e-3;
285  offset_ = 0;
286  ruler_offset_ = 0;
287  zero_offset_ = 0;
288  custom_zero_offset_set_ = false;
289  updating_scroll_ = false;
290  restoring_state_ = false;
291  always_zoom_to_fit_ = false;
292  tick_period_ = 0;
294  tick_precision_ = 0;
296  show_cursors_ = false;
297  cursors_ = make_shared<CursorPair>(*this);
298  next_flag_text_ = 'A';
299  trigger_markers_.clear();
300  hover_widget_ = nullptr;
301  grabbed_widget_ = nullptr;
302  hover_point_ = QPoint(-1, -1);
303  scroll_needs_defaults_ = true;
306 
307  show_cursors_ = false;
309  flags_.clear();
310 
311  // Update the zoom state
313 
314  // Make sure the standard bar's segment selector is in sync
316 
317  scrollarea_->verticalScrollBar()->setRange(-100000000, 100000000);
318 }
319 
321 {
322  return session_;
323 }
324 
325 const Session& View::session() const
326 {
327  return session_;
328 }
329 
330 vector< shared_ptr<Signal> > View::signals() const
331 {
332  return signals_;
333 }
334 
335 shared_ptr<Signal> View::get_signal_by_signalbase(shared_ptr<data::SignalBase> base) const
336 {
337  shared_ptr<Signal> ret_val;
338 
339  for (const shared_ptr<Signal>& s : signals_)
340  if (s->base() == base) {
341  ret_val = s;
342  break;
343  }
344 
345  return ret_val;
346 }
347 
349 {
351  signals_.clear();
352 }
353 
354 void View::add_signalbase(const shared_ptr<data::SignalBase> signalbase)
355 {
356  ViewBase::add_signalbase(signalbase);
357 
358  shared_ptr<Signal> signal;
359 
360  switch (signalbase->type()) {
361  case SignalBase::LogicChannel:
362  signal = shared_ptr<Signal>(new LogicSignal(session_, signalbase));
363  break;
364 
365  case SignalBase::AnalogChannel:
366  signal = shared_ptr<Signal>(new AnalogSignal(session_, signalbase));
367  break;
368 
369  case SignalBase::MathChannel:
370  signal = shared_ptr<Signal>(new MathSignal(session_, signalbase));
371  break;
372 
373  default:
374  qDebug() << "Unknown signalbase type:" << signalbase->type();
375  assert(false);
376  break;
377  }
378 
379  signals_.push_back(signal);
380 
381  signal->set_segment_display_mode(segment_display_mode_);
382  signal->set_current_segment(current_segment_);
383 
384  // Secondary views use the signal's settings in the main view
385  if (!is_main_view()) {
386  shared_ptr<View> main_tv = dynamic_pointer_cast<View>(session_.main_view());
387  shared_ptr<Signal> main_signal = main_tv->get_signal_by_signalbase(signalbase);
388  if (main_signal)
389  signal->restore_settings(main_signal->save_settings());
390  }
391 
392  connect(signal->base().get(), SIGNAL(name_changed(const QString&)),
393  this, SLOT(on_signal_name_changed()));
394 }
395 
396 void View::remove_signalbase(const shared_ptr<data::SignalBase> signalbase)
397 {
398  ViewBase::remove_signalbase(signalbase);
399 
400  shared_ptr<Signal> signal = get_signal_by_signalbase(signalbase);
401 
402  if (signal)
403  remove_trace(signal);
404 }
405 
406 #ifdef ENABLE_DECODE
407 void View::clear_decode_signals()
408 {
409  ViewBase::clear_decode_signals();
410  decode_traces_.clear();
411 }
412 
413 void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
414 {
415  ViewBase::add_decode_signal(signal);
416 
417  shared_ptr<DecodeTrace> d(
418  new DecodeTrace(session_, signal, decode_traces_.size()));
419  decode_traces_.push_back(d);
420 
421  d->set_segment_display_mode(segment_display_mode_);
422  d->set_current_segment(current_segment_);
423 
424  connect(signal.get(), SIGNAL(name_changed(const QString&)),
425  this, SLOT(on_signal_name_changed()));
426 }
427 
428 void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
429 {
430  for (auto i = decode_traces_.begin(); i != decode_traces_.end(); i++)
431  if ((*i)->base() == signal) {
432  decode_traces_.erase(i);
433  break;
434  }
435 
436  ViewBase::remove_decode_signal(signal);
437 }
438 #endif
439 
440 void View::remove_trace(shared_ptr<Trace> trace)
441 {
442  TraceTreeItemOwner *const owner = trace->owner();
443  assert(owner);
444  owner->remove_child_item(trace);
445 
446  for (auto i = signals_.begin(); i != signals_.end(); i++)
447  if ((*i) == trace) {
448  signals_.erase(i);
449  break;
450  }
451 
452  if (!header_was_shrunk_)
454 
455  update_layout();
456 
457  header_->update();
458  viewport_->update();
459 }
460 
461 shared_ptr<Signal> View::get_signal_under_mouse_cursor() const
462 {
464 }
465 
467 {
468  return this;
469 }
470 
471 const View* View::view() const
472 {
473  return this;
474 }
475 
477 {
478  return viewport_;
479 }
480 
481 const Viewport* View::viewport() const
482 {
483  return viewport_;
484 }
485 
487 {
488  return scrollarea_;
489 }
490 
491 const Ruler* View::ruler() const
492 {
493  return ruler_;
494 }
495 
496 void View::save_settings(QSettings &settings) const
497 {
498  settings.setValue("scale", scale_);
499  settings.setValue("v_offset",
500  scrollarea_->verticalScrollBar()->sliderPosition());
501 
502  settings.setValue("splitter_state", splitter_->saveState());
503  settings.setValue("segment_display_mode", segment_display_mode_);
504 
505  GlobalSettings::store_timestamp(settings, "offset", offset_);
506 
508  GlobalSettings::store_timestamp(settings, "zero_offset", -zero_offset_);
509  else
510  settings.remove("zero_offset");
511 
512  for (const shared_ptr<Signal>& signal : signals_) {
513  settings.beginGroup(signal->base()->internal_name());
514  signal->save_settings(settings);
515  settings.endGroup();
516  }
517 }
518 
520 {
521  // Note: It is assumed that this function is only called once,
522  // immediately after restoring a previous session.
523 
524  if (settings.contains("scale"))
525  set_scale(settings.value("scale").toDouble());
526 
527  if (settings.contains("offset")) {
528  // This also updates ruler_offset_
529  set_offset(GlobalSettings::restore_timestamp(settings, "offset"));
530  }
531 
532  if (settings.contains("zero_offset"))
533  set_zero_position(GlobalSettings::restore_timestamp(settings, "zero_offset"));
534 
535  if (settings.contains("splitter_state"))
536  splitter_->restoreState(settings.value("splitter_state").toByteArray());
537 
538  if (settings.contains("segment_display_mode"))
540  (Trace::SegmentDisplayMode)(settings.value("segment_display_mode").toInt()));
541 
542  for (shared_ptr<Signal> signal : signals_) {
543  settings.beginGroup(signal->base()->internal_name());
544  signal->restore_settings(settings);
545  settings.endGroup();
546  }
547 
548  if (settings.contains("v_offset")) {
549  // Note: see eventFilter() for additional information
550  saved_v_offset_ = settings.value("v_offset").toInt();
551  scroll_needs_defaults_ = false;
552  }
553 
554  restoring_state_ = true;
555 
556  // Update the ruler so that it uses the new scale
558 }
559 
560 vector< shared_ptr<TimeItem> > View::time_items() const
561 {
562  const vector<shared_ptr<Flag>> f(flags());
563  vector<shared_ptr<TimeItem>> items(f.begin(), f.end());
564 
565  if (cursors_) {
566  items.push_back(cursors_);
567  items.push_back(cursors_->first());
568  items.push_back(cursors_->second());
569  }
570 
571  for (auto& trigger_marker : trigger_markers_)
572  items.push_back(trigger_marker);
573 
574  return items;
575 }
576 
577 double View::scale() const
578 {
579  return scale_;
580 }
581 
582 void View::set_scale(double scale)
583 {
584  if (scale_ != scale) {
585  scale_ = scale;
586 
588 
589  scale_changed();
590  }
591 }
592 
593 void View::set_offset(const pv::util::Timestamp& offset, bool force_update)
594 {
595  if ((offset_ != offset) || force_update) {
596  offset_ = offset;
598 
600 
601  offset_changed();
602  }
603 }
604 
605 const Timestamp& View::offset() const
606 {
607  return offset_;
608 }
609 
611 {
612  return ruler_offset_;
613 }
614 
616 {
617  zero_offset_ = -position;
619 
620  // Force an immediate update of the offsets
621  set_offset(offset_, true);
622  ruler_->update();
623 }
624 
626 {
627  zero_offset_ = 0;
628 
629  // When enabled, the first trigger for this segment is used as the zero position
630  GlobalSettings settings;
631  bool trigger_is_zero_time = settings.value(GlobalSettings::Key_View_TriggerIsZeroTime).toBool();
632 
633  if (trigger_is_zero_time) {
634  vector<util::Timestamp> triggers = session_.get_triggers(current_segment_);
635 
636  if (triggers.size() > 0)
637  zero_offset_ = triggers.front();
638  }
639 
640  custom_zero_offset_set_ = false;
641 
642  // Force an immediate update of the offsets
643  set_offset(offset_, true);
644  ruler_->update();
645 }
646 
648 {
649  return zero_offset_;
650 }
651 
653 {
654  return -scrollarea_->verticalScrollBar()->sliderPosition();
655 }
656 
658 {
659  scrollarea_->verticalScrollBar()->setSliderPosition(offset);
660  header_->update();
661  viewport_->update();
662 }
663 
665 {
666  scrollarea_->horizontalScrollBar()->setSliderPosition(offset);
667  header_->update();
668  viewport_->update();
669 }
670 
672 {
673  return scrollarea_->horizontalScrollBar()->maximum();
674 }
675 
676 unsigned int View::depth() const
677 {
678  return 0;
679 }
680 
681 uint32_t View::current_segment() const
682 {
683  return current_segment_;
684 }
685 
687 {
688  return tick_prefix_;
689 }
690 
692 {
693  if (tick_prefix_ != tick_prefix) {
696  }
697 }
698 
699 unsigned int View::tick_precision() const
700 {
701  return tick_precision_;
702 }
703 
705 {
706  if (tick_precision_ != tick_precision) {
709  }
710 }
711 
713 {
714  return tick_period_;
715 }
716 
717 unsigned int View::minor_tick_count() const
718 {
719  return minor_tick_count_;
720 }
721 
723 {
724  if (tick_period_ != tick_period) {
727  }
728 }
729 
731 {
732  return time_unit_;
733 }
734 
736 {
737  if (time_unit_ != time_unit) {
740  }
741 }
742 
743 void View::set_current_segment(uint32_t segment_id)
744 {
745  current_segment_ = segment_id;
746 
747  for (const shared_ptr<Signal>& signal : signals_)
748  signal->set_current_segment(current_segment_);
749 #ifdef ENABLE_DECODE
750  for (shared_ptr<DecodeTrace>& dt : decode_traces_)
751  dt->set_current_segment(current_segment_);
752 #endif
753 
754  vector<util::Timestamp> triggers = session_.get_triggers(current_segment_);
755 
756  trigger_markers_.clear();
757  for (util::Timestamp timestamp : triggers)
758  trigger_markers_.push_back(make_shared<TriggerMarker>(*this, timestamp));
759 
762 
763  viewport_->update();
764 
765  segment_changed(segment_id);
766 }
767 
769 {
770  return segment_selectable_;
771 }
772 
774 {
775  return segment_display_mode_;
776 }
777 
779 {
780  segment_display_mode_ = mode;
781 
782  for (const shared_ptr<Signal>& signal : signals_)
783  signal->set_segment_display_mode(mode);
784 
785  uint32_t last_segment = session_.get_highest_segment_id();
786 
787  switch (mode) {
789  if (current_segment_ != last_segment)
790  set_current_segment(last_segment);
791  break;
792 
794  // Do nothing if we only have one segment so far
795  if (last_segment > 0) {
796  // If the last segment isn't complete, the previous one must be
797  uint32_t segment_id =
798  (session_.all_segments_complete(last_segment)) ?
799  last_segment : last_segment - 1;
800 
801  if (current_segment_ != segment_id)
802  set_current_segment(segment_id);
803  }
804  break;
805 
809  default:
810  // Current segment remains as-is
811  break;
812  }
813 
814  segment_selectable_ = true;
815 
816  if ((mode == Trace::ShowAllSegments) || (mode == Trace::ShowAccumulatedIntensity))
817  segment_selectable_ = false;
818 
819  viewport_->update();
820 
822 }
823 
824 void View::zoom(double steps)
825 {
826  zoom(steps, viewport_->width() / 2);
827 }
828 
829 void View::zoom(double steps, int offset)
830 {
831  set_zoom(scale_ * pow(3.0 / 2.0, -steps), offset);
832 }
833 
834 void View::zoom_fit(bool gui_state)
835 {
836  // Act as one-shot when stopped, toggle along with the GUI otherwise
838  always_zoom_to_fit_ = false;
840  } else {
841  always_zoom_to_fit_ = gui_state;
842  always_zoom_to_fit_changed(gui_state);
843  }
844 
845  const pair<Timestamp, Timestamp> extents = get_time_extents();
846  const Timestamp delta = extents.second - extents.first;
847  if (delta < Timestamp("1e-12"))
848  return;
849 
850  assert(viewport_);
851  const int w = viewport_->width();
852  if (w <= 0)
853  return;
854 
855  const Timestamp scale = max(min(delta / w, MaxScale), MinScale);
856  set_scale_offset(scale.convert_to<double>(), extents.first);
857 }
858 
859 void View::focus_on_range(uint64_t start_sample, uint64_t end_sample)
860 {
861  assert(viewport_);
862  const uint64_t w = viewport_->width();
863  if (w <= 0)
864  return;
865 
866  const double samplerate = session_.get_samplerate();
867  const double samples_per_pixel = samplerate * scale_;
868  const uint64_t viewport_samples = w * samples_per_pixel;
869 
870  const uint64_t sample_delta = (end_sample - start_sample);
871 
872  // Note: We add 20% margin on the left and 5% on the right
873  const uint64_t ext_sample_delta = sample_delta * 1.25;
874 
875  // Check if we can keep the zoom level and just center the supplied range
876  if (viewport_samples >= ext_sample_delta) {
877  // Note: offset is the left edge of the view so to center, we subtract half the view width
878  const int64_t sample_offset = (start_sample + (sample_delta / 2) - (viewport_samples / 2));
879  const Timestamp offset = sample_offset / samplerate;
880  set_scale_offset(scale_, offset);
881  } else {
882  const Timestamp offset = (start_sample - sample_delta * 0.20) / samplerate;
883  const Timestamp delta = ext_sample_delta / samplerate;
884  const Timestamp scale = max(min(delta / w, MaxScale), MinScale);
885  set_scale_offset(scale.convert_to<double>(), offset);
886  }
887 }
888 
890 {
891  // Disable sticky scrolling / always zoom to fit when acquisition runs
892  // and user drags the viewport
893  if ((scale_ == scale) && (offset_ != offset) &&
895 
896  if (sticky_scrolling_) {
897  sticky_scrolling_ = false;
899  }
900 
901  if (always_zoom_to_fit_) {
902  always_zoom_to_fit_ = false;
904  }
905  }
906 
907  set_scale(scale);
908  set_offset(offset);
909 
911 
912  update_scroll();
913  ruler_->update();
914  viewport_->update();
915 }
916 
917 vector< shared_ptr<SignalData> > View::get_visible_data() const
918 {
919  // Make a set of all the visible data objects
920  vector< shared_ptr<SignalData> > visible_data;
921  for (const shared_ptr<Signal>& sig : signals_)
922  if (sig->enabled())
923  visible_data.push_back(sig->base()->data());
924 
925  return visible_data;
926 }
927 
928 pair<Timestamp, Timestamp> View::get_time_extents() const
929 {
930  boost::optional<Timestamp> left_time, right_time;
931 
932  vector< shared_ptr<SignalData> > data;
933  if (signals_.size() == 0)
934  return make_pair(0, 0);
935 
936  for (const shared_ptr<Signal>& s : signals_)
937  if (s->base()->data() && (s->base()->data()->segments().size() > 0))
938  data.push_back(s->base()->data());
939 
940  for (const shared_ptr<SignalData>& d : data) {
941  const vector< shared_ptr<Segment> > segments = d->segments();
942  for (const shared_ptr<Segment>& s : segments) {
943  double samplerate = s->samplerate();
944  samplerate = (samplerate <= 0.0) ? 1.0 : samplerate;
945 
946  const Timestamp start_time = s->start_time();
947  left_time = left_time ?
948  min(*left_time, start_time) :
949  start_time;
950  right_time = right_time ?
951  max(*right_time, start_time + d->max_sample_count() / samplerate) :
952  start_time + d->max_sample_count() / samplerate;
953  }
954  }
955 
956  if (!left_time || !right_time)
957  return make_pair(0, 0);
958 
959  assert(*left_time <= *right_time);
960  return make_pair(*left_time, *right_time);
961 }
962 
963 bool View::colored_bg() const
964 {
965  return colored_bg_;
966 }
967 
969 {
970  return show_cursors_;
971 }
972 
973 void View::show_cursors(bool show)
974 {
975  if (show_cursors_ != show) {
976  show_cursors_ = show;
977 
978  cursor_state_changed(show);
979  ruler_->update();
980  viewport_->update();
981  }
982 }
983 
985 {
986  assert(cursors_);
987 
988  cursors_->first()->set_time(first);
989  cursors_->second()->set_time(second);
990 
991  ruler_->update();
992  viewport_->update();
993 }
994 
996 {
997  assert(cursors_);
998 
999  const double time_width = scale_ * viewport_->width();
1000  cursors_->first()->set_time(offset_ + time_width * 0.4);
1001  cursors_->second()->set_time(offset_ + time_width * 0.6);
1002 
1003  ruler_->update();
1004  viewport_->update();
1005 }
1006 
1007 shared_ptr<CursorPair> View::cursors() const
1008 {
1009  return cursors_;
1010 }
1011 
1012 shared_ptr<Flag> View::add_flag(const Timestamp& time)
1013 {
1014  shared_ptr<Flag> flag =
1015  make_shared<Flag>(*this, time, QString("%1").arg(next_flag_text_));
1016  flags_.push_back(flag);
1017 
1018  next_flag_text_ = (next_flag_text_ >= 'Z') ? 'A' :
1019  (next_flag_text_ + 1);
1020 
1021  time_item_appearance_changed(true, true);
1022  return flag;
1023 }
1024 
1025 void View::remove_flag(shared_ptr<Flag> flag)
1026 {
1027  flags_.remove(flag);
1028  time_item_appearance_changed(true, true);
1029 }
1030 
1031 vector< shared_ptr<Flag> > View::flags() const
1032 {
1033  vector< shared_ptr<Flag> > flags(flags_.begin(), flags_.end());
1034  stable_sort(flags.begin(), flags.end(),
1035  [](const shared_ptr<Flag> &a, const shared_ptr<Flag> &b) {
1036  return a->time() < b->time();
1037  });
1038 
1039  return flags;
1040 }
1041 
1042 const QPoint& View::hover_point() const
1043 {
1044  return hover_point_;
1045 }
1046 
1047 const QWidget* View::hover_widget() const
1048 {
1049  return hover_widget_;
1050 }
1051 
1052 int64_t View::get_nearest_level_change(const QPoint &p)
1053 {
1054  // Is snapping disabled?
1055  if (snap_distance_ == 0)
1056  return -1;
1057 
1058  struct entry_t {
1059  entry_t(shared_ptr<Signal> s) :
1060  signal(s), delta(numeric_limits<int64_t>::max()), sample(-1), is_dense(false) {}
1061  shared_ptr<Signal> signal;
1062  int64_t delta;
1063  int64_t sample;
1064  bool is_dense;
1065  };
1066 
1067  vector<entry_t> list;
1068 
1069  // Create list of signals to consider
1071  list.emplace_back(signal_under_mouse_cursor_);
1072  else
1073  for (shared_ptr<Signal> s : signals_) {
1074  if (!s->enabled())
1075  continue;
1076 
1077  list.emplace_back(s);
1078  }
1079 
1080  // Get data for listed signals
1081  for (entry_t &e : list) {
1082  // Calculate sample number from cursor position
1083  const double samples_per_pixel = e.signal->base()->get_samplerate() * scale();
1084  const int64_t x_offset = offset().convert_to<double>() / scale();
1085  const int64_t sample_num = max(((x_offset + p.x()) * samples_per_pixel), 0.0);
1086 
1087  vector<data::LogicSegment::EdgePair> edges =
1088  e.signal->get_nearest_level_changes(sample_num);
1089 
1090  if (edges.empty())
1091  continue;
1092 
1093  // Check first edge
1094  const int64_t first_sample_delta = abs(sample_num - edges.front().first);
1095  const int64_t first_delta = first_sample_delta / samples_per_pixel;
1096  e.delta = first_delta;
1097  e.sample = edges.front().first;
1098 
1099  // Check second edge if available
1100  if (edges.size() == 2) {
1101  // Note: -1 because this is usually the right edge and sample points are left-aligned
1102  const int64_t second_sample_delta = abs(sample_num - edges.back().first - 1);
1103  const int64_t second_delta = second_sample_delta / samples_per_pixel;
1104 
1105  // If both edges are too close, we mark this signal as being dense
1106  if ((first_delta + second_delta) <= snap_distance_)
1107  e.is_dense = true;
1108 
1109  if (second_delta < first_delta) {
1110  e.delta = second_delta;
1111  e.sample = edges.back().first;
1112  }
1113  }
1114  }
1115 
1116  // Look for the best match: non-dense first, then dense
1117  entry_t *match = nullptr;
1118 
1119  for (entry_t &e : list) {
1120  if (e.delta > snap_distance_ || e.is_dense)
1121  continue;
1122 
1123  if (match) {
1124  if (e.delta < match->delta)
1125  match = &e;
1126  } else
1127  match = &e;
1128  }
1129 
1130  if (!match) {
1131  for (entry_t &e : list) {
1132  if (!e.is_dense)
1133  continue;
1134 
1135  if (match) {
1136  if (e.delta < match->delta)
1137  match = &e;
1138  } else
1139  match = &e;
1140  }
1141  }
1142 
1143  if (match) {
1144  // Somewhat ugly hack to make TimeItem::drag_by() work
1145  signal_under_mouse_cursor_ = match->signal;
1146 
1147  return match->sample;
1148  }
1149 
1150  return -1;
1151 }
1152 
1154 {
1155  // Make a list of owners that is sorted from deepest first
1156  const vector<shared_ptr<TraceTreeItem>> items(
1157  list_by_type<TraceTreeItem>());
1158  set< TraceTreeItemOwner* > owners;
1159  for (const auto &r : items)
1160  owners.insert(r->owner());
1161  vector< TraceTreeItemOwner* > sorted_owners(owners.begin(), owners.end());
1162  sort(sorted_owners.begin(), sorted_owners.end(),
1163  [](const TraceTreeItemOwner* a, const TraceTreeItemOwner *b) {
1164  return a->depth() > b->depth(); });
1165 
1166  // Restack the items recursively
1167  for (auto &o : sorted_owners)
1168  o->restack_items();
1169 
1170  // Animate the items to their destination
1171  for (const auto &i : items)
1172  i->animate_to_layout_v_offset();
1173 }
1174 
1176 {
1177  return header_->extended_size_hint().width();
1178 }
1179 
1180 void View::on_setting_changed(const QString &key, const QVariant &value)
1181 {
1182  GlobalSettings settings;
1183 
1185  colored_bg_ = settings.value(GlobalSettings::Key_View_ColoredBG).toBool();
1186  viewport_->update();
1187  }
1188 
1191  viewport_->update();
1192 
1195 
1197  snap_distance_ = settings.value(GlobalSettings::Key_View_SnapDistance).toInt();
1198 }
1199 
1200 void View::trigger_event(int segment_id, util::Timestamp location)
1201 {
1202  // TODO This doesn't work if we're showing multiple segments at once
1203  if ((uint32_t)segment_id != current_segment_)
1204  return;
1205 
1208 
1209  trigger_markers_.push_back(make_shared<TriggerMarker>(*this, location));
1210 }
1211 
1212 void View::get_scroll_layout(double &length, Timestamp &offset) const
1213 {
1214  const pair<Timestamp, Timestamp> extents = get_time_extents();
1215  length = ((extents.second - extents.first) / scale_).convert_to<double>();
1216  offset = offset_ / scale_;
1217 }
1218 
1219 void View::set_zoom(double scale, int offset)
1220 {
1221  // Reset the "always zoom to fit" feature as the user changed the zoom
1222  always_zoom_to_fit_ = false;
1224 
1225  const Timestamp cursor_offset = offset_ + scale_ * offset;
1226  const Timestamp new_scale = max(min(Timestamp(scale), MaxScale), MinScale);
1227  const Timestamp new_offset = cursor_offset - new_scale * offset;
1228  set_scale_offset(new_scale.convert_to<double>(), new_offset);
1229 }
1230 
1232 {
1233  const double SpacingIncrement = 10.0f;
1234  const double MinValueSpacing = 40.0f;
1235 
1236  // Figure out the highest numeric value visible on a label
1237  const QSize areaSize = viewport_->size();
1238  const Timestamp max_time = max(fabs(offset_),
1239  fabs(offset_ + scale_ * areaSize.width()));
1240 
1241  double min_width = SpacingIncrement;
1242  double label_width, tick_period_width;
1243 
1244  QFontMetrics m(QApplication::font());
1245 
1246  // Copies of the member variables with the same name, used in the calculation
1247  // and written back afterwards, so that we don't emit signals all the time
1248  // during the calculation.
1251  unsigned tick_precision = tick_precision_;
1252 
1253  do {
1254  const double min_period = scale_ * min_width;
1255 
1256  const int order = (int)floorf(log10f(min_period));
1257  const pv::util::Timestamp order_decimal =
1258  pow(pv::util::Timestamp(10), order);
1259 
1260  // Allow for a margin of error so that a scale unit of 1 can be used.
1261  // Otherwise, for a SU of 1 the tick period will almost always be below
1262  // the min_period by a small amount - and thus skipped in favor of 2.
1263  // Note: margin assumes that SU[0] and SU[1] contain the smallest values
1264  double tp_margin = (ScaleUnits[0] + ScaleUnits[1]) / 2.0;
1265  double tp_with_margin;
1266  unsigned int unit = 0;
1267 
1268  do {
1269  tp_with_margin = order_decimal.convert_to<double>() *
1270  (ScaleUnits[unit++] + tp_margin);
1271  } while (tp_with_margin < min_period && unit < countof(ScaleUnits));
1272 
1273  minor_tick_count_ = (unit == 2) ? 4 : 5;
1274  tick_period = order_decimal * ScaleUnits[unit - 1];
1275  tick_prefix = static_cast<pv::util::SIPrefix>(
1277 
1278  // Precision is the number of fractional digits required, not
1279  // taking the prefix into account (and it must never be negative)
1280  tick_precision = max(ceil(log10(1 / tick_period)).convert_to<int>(), 0);
1281 
1282  tick_period_width = (tick_period / scale_).convert_to<double>();
1283 
1284  const QString label_text = Ruler::format_time_with_distance(
1285  tick_period, max_time, tick_prefix, time_unit_, tick_precision);
1286 
1287  label_width = m.boundingRect(0, 0, INT_MAX, INT_MAX,
1288  Qt::AlignLeft | Qt::AlignTop, label_text).width() +
1289  MinValueSpacing;
1290 
1291  min_width += SpacingIncrement;
1292  } while (tick_period_width < label_width);
1293 
1294  set_tick_period(tick_period);
1295  set_tick_prefix(tick_prefix);
1296  set_tick_precision(tick_precision);
1297 }
1298 
1300 {
1301  assert(viewport_);
1302 
1303  const QSize areaSize = viewport_->size();
1304 
1305  const pair<int, int> extents = v_extents();
1306  const int top_margin = owner_visual_v_offset() + extents.first;
1307  const int trace_bottom = owner_visual_v_offset() + extents.first + extents.second;
1308 
1309  // Do we have empty space at the top while the last trace goes out of screen?
1310  if ((top_margin > 0) && (trace_bottom > areaSize.height())) {
1311  const int trace_height = extents.second - extents.first;
1312 
1313  // Center everything vertically if there is enough space
1314  if (areaSize.height() >= trace_height)
1315  set_v_offset(extents.first -
1316  ((areaSize.height() - trace_height) / 2));
1317  else
1318  // Remove the top margin to make as many traces fit on screen as possible
1319  set_v_offset(extents.first);
1320  }
1321 }
1322 
1324 {
1325  assert(viewport_);
1326  QScrollBar *hscrollbar = scrollarea_->horizontalScrollBar();
1327  QScrollBar *vscrollbar = scrollarea_->verticalScrollBar();
1328 
1329  const QSize areaSize = viewport_->size();
1330 
1331  // Set the horizontal scroll bar
1332  double length = 0;
1333  Timestamp offset;
1334  get_scroll_layout(length, offset);
1335  length = max(length - areaSize.width(), 0.0);
1336 
1337  int major_tick_distance = (tick_period_ / scale_).convert_to<int>();
1338 
1339  hscrollbar->setPageStep(areaSize.width() / 2);
1340  hscrollbar->setSingleStep(major_tick_distance);
1341 
1342  updating_scroll_ = true;
1343 
1344  if (length < MaxScrollValue) {
1345  hscrollbar->setRange(0, length);
1346  hscrollbar->setSliderPosition(offset.convert_to<double>());
1347  } else {
1348  hscrollbar->setRange(0, MaxScrollValue);
1349  hscrollbar->setSliderPosition(
1350  (offset_ * MaxScrollValue / (scale_ * length)).convert_to<double>());
1351  }
1352 
1353  updating_scroll_ = false;
1354 
1355  // Set the vertical scrollbar
1356  vscrollbar->setPageStep(areaSize.height());
1357  vscrollbar->setSingleStep(areaSize.height() / 8);
1358 
1359  const pair<int, int> extents = v_extents();
1360 
1361  // Don't change the scrollbar range if there are no traces
1362  if (extents.first != extents.second) {
1363  int top_margin = ViewScrollMargin;
1364  int btm_margin = ViewScrollMargin;
1365 
1366  vscrollbar->setRange(extents.first - areaSize.height() + top_margin,
1367  extents.second - btm_margin);
1368  }
1369 
1370  if (scroll_needs_defaults_) {
1372  scroll_needs_defaults_ = false;
1373  }
1374 }
1375 
1377 {
1378  scrollarea_->verticalScrollBar()->setRange(0, 0);
1379 }
1380 
1382 {
1383  assert(viewport_);
1384 
1385  const QSize areaSize = viewport_->size();
1386 
1387  const pair<int, int> extents = v_extents();
1388  const int trace_height = extents.second - extents.first;
1389 
1390  // Do all traces fit in the view?
1391  if (areaSize.height() >= trace_height)
1392  // Center all traces vertically
1393  set_v_offset(extents.first -
1394  ((areaSize.height() - trace_height) / 2));
1395  else
1396  // Put the first trace at the top, letting the bottom ones overflow
1397  set_v_offset(extents.first);
1398 }
1399 
1401 {
1402  const int header_pane_width =
1403  splitter_->sizes().front(); // clazy:exclude=detaching-temporary
1404 
1405  // Allow for a slight margin of error so that we also accept
1406  // slight differences when e.g. a label name change increased
1407  // the overall width
1408  header_was_shrunk_ = (header_pane_width < (header_width() - 10));
1409 }
1410 
1412 {
1413  // Setting the maximum width of the header widget doesn't work as
1414  // expected because the splitter would allow the user to make the
1415  // pane wider than that, creating empty space as a result.
1416  // To make this work, we stricly enforce the maximum width by
1417  // expanding the header unless the user shrunk it on purpose.
1418  // As we're then setting the width of the header pane, we set the
1419  // splitter to the maximum allowed position.
1420 
1421  int splitter_area_width = 0;
1422  for (int w : splitter_->sizes()) // clazy:exclude=range-loop
1423  splitter_area_width += w;
1424 
1425  // Make sure the header has enough horizontal space to show all labels fully
1426  QList<int> pane_sizes;
1427  pane_sizes.push_back(header_->extended_size_hint().width());
1428  pane_sizes.push_back(splitter_area_width - header_->extended_size_hint().width());
1429  splitter_->setSizes(pane_sizes);
1430 }
1431 
1433 {
1434  update_scroll();
1435 
1437 }
1438 
1440  const shared_ptr<sigrok::ChannelGroup> &group,
1441  const map<shared_ptr<data::SignalBase>, shared_ptr<Signal> > &signal_map)
1442 {
1443  assert(group);
1444 
1445  set<TraceTreeItemOwner*> owners;
1446  vector<TraceTreeItemOwner*> owner_list;
1447 
1448  // Make a set and a list of all the owners
1449  for (const auto& channel : group->channels()) {
1450  for (auto& entry : signal_map) {
1451  if (entry.first->channel() == channel) {
1452  TraceTreeItemOwner *const o = (entry.second)->owner();
1453  owner_list.push_back(o);
1454  owners.insert(o);
1455  }
1456  }
1457  }
1458 
1459  // Iterate through the list of owners, and find the most prevalent
1460  size_t max_prevalence = 0;
1461  TraceTreeItemOwner *prevalent_owner = nullptr;
1462  for (TraceTreeItemOwner *owner : owners) {
1463  const size_t prevalence = count_if(
1464  owner_list.begin(), owner_list.end(),
1465  [&](TraceTreeItemOwner *o) { return o == owner; });
1466  if (prevalence > max_prevalence) {
1467  max_prevalence = prevalence;
1468  prevalent_owner = owner;
1469  }
1470  }
1471 
1472  return prevalent_owner;
1473 }
1474 
1475 vector< shared_ptr<Trace> > View::extract_new_traces_for_channels(
1476  const vector< shared_ptr<sigrok::Channel> > &channels,
1477  const map<shared_ptr<data::SignalBase>, shared_ptr<Signal> > &signal_map,
1478  set< shared_ptr<Trace> > &add_list)
1479 {
1480  vector< shared_ptr<Trace> > filtered_traces;
1481 
1482  for (const auto& channel : channels) {
1483  for (auto& entry : signal_map) {
1484  if (entry.first->channel() == channel) {
1485  shared_ptr<Trace> trace = entry.second;
1486  const auto list_iter = add_list.find(trace);
1487  if (list_iter == add_list.end())
1488  continue;
1489 
1490  filtered_traces.push_back(trace);
1491  add_list.erase(list_iter);
1492  }
1493  }
1494  }
1495 
1496  return filtered_traces;
1497 }
1498 
1500 {
1501  // Check whether we know the sample rate and hence can use time as the unit
1503  // Check all signals but...
1504  for (const shared_ptr<Signal>& signal : signals_) {
1505  const shared_ptr<SignalData> data = signal->base()->data();
1506 
1507  // ...only check first segment of each
1508  const vector< shared_ptr<Segment> > segments = data->segments();
1509  if (!segments.empty())
1510  if (segments[0]->samplerate()) {
1512  break;
1513  }
1514  }
1515  }
1516 }
1517 
1518 bool View::eventFilter(QObject *object, QEvent *event)
1519 {
1520  const QEvent::Type type = event->type();
1521 
1522  if (type == QEvent::MouseMove) {
1523 
1524  if (object)
1525  hover_widget_ = qobject_cast<QWidget*>(object);
1526 
1527  const QMouseEvent *const mouse_event = (QMouseEvent*)event;
1528  if (object == viewport_)
1529  hover_point_ = mouse_event->pos();
1530  else if (object == ruler_)
1531  hover_point_ = mouse_event->pos();
1532  else if (object == header_)
1533 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1534  hover_point_ = QPoint(0, mouse_event->pos().y());
1535 #else
1536  hover_point_ = QPoint(0, mouse_event->y());
1537 #endif
1538  else
1539  hover_point_ = QPoint(-1, -1);
1540 
1542 
1543  if (grabbed_widget_) {
1544  int64_t nearest = get_nearest_level_change(hover_point_);
1545  pv::util::Timestamp mouse_time = offset_ + hover_point_.x() * scale_;
1546 
1547  if (nearest == -1) {
1548  grabbed_widget_->set_time(mouse_time);
1549  } else {
1550  grabbed_widget_->set_time(nearest / get_signal_under_mouse_cursor()->base()->get_samplerate());
1551  }
1552  }
1553 
1554  } else if (type == QEvent::MouseButtonPress) {
1555  grabbed_widget_ = nullptr;
1556 
1557  const QMouseEvent *const mouse_event = (QMouseEvent*)event;
1558  if ((object == viewport_) && (mouse_event->button() & Qt::LeftButton)) {
1559  // Send event to all trace tree items
1560  const vector<shared_ptr<TraceTreeItem>> trace_tree_items(
1561  list_by_type<TraceTreeItem>());
1562  for (const shared_ptr<TraceTreeItem>& r : trace_tree_items)
1563  r->mouse_left_press_event(mouse_event);
1564  }
1565  } else if (type == QEvent::Leave) {
1566  hover_point_ = QPoint(-1, -1);
1568  } else if (type == QEvent::Show) {
1569 
1570  // This is somewhat of a hack, unfortunately. We cannot use
1571  // set_v_offset() from within restore_settings() as the view
1572  // at that point is neither visible nor properly sized.
1573  // This is the least intrusive workaround I could come up
1574  // with: set the vertical offset (or scroll defaults) when
1575  // the view is shown, which happens after all widgets were
1576  // resized to their final sizes.
1577  update_layout();
1578 
1579  if (restoring_state_)
1581  else
1583 
1584  if (scroll_needs_defaults_) {
1586  scroll_needs_defaults_ = false;
1587  }
1588 
1589  if (restoring_state_)
1591  }
1592 
1593  return QObject::eventFilter(object, event);
1594 }
1595 
1596 void View::contextMenuEvent(QContextMenuEvent *event)
1597 {
1598  QPoint pos = event->pos() - QPoint(0, ruler_->sizeHint().height());
1599 
1600  const shared_ptr<ViewItem> r = viewport_->get_mouse_over_item(pos);
1601 
1602  QMenu* menu = nullptr;
1603 
1604  if (!r) {
1605  context_menu_x_pos_ = pos.x();
1606 
1607  // No view item under cursor, use generic menu
1608  menu = new QMenu(this);
1609 
1610  QAction *const create_marker_here = new QAction(tr("Create marker here"), this);
1611  connect(create_marker_here, SIGNAL(triggered()), this, SLOT(on_create_marker_here()));
1612  menu->addAction(create_marker_here);
1613  } else {
1614  menu = r->create_view_context_menu(this, pos);
1615  }
1616 
1617  if (menu)
1618  menu->popup(event->globalPos());
1619 }
1620 
1621 void View::resizeEvent(QResizeEvent* event)
1622 {
1623  // Only adjust the top margin if we shrunk vertically
1624  if (event->size().height() < event->oldSize().height())
1626 
1627  update_layout();
1628 }
1629 
1631 {
1632  const int w = viewport_->width();
1633  if (w > 0) {
1634  const double samplerate = session_.get_samplerate();
1635  // Note: sample_num = time * samplerate
1636  // Note: samples_per_pixel = samplerate * scale
1637  const int64_t start_sample = (offset_ * samplerate).convert_to<int64_t>();
1638  const int64_t end_sample = (offset_ * samplerate).convert_to<int64_t>() +
1639  (w * session_.get_samplerate() * scale_);
1640 
1641  MetadataObject* md_obj =
1643 
1644  const int64_t old_start_sample = md_obj->value(MetadataValueStartSample).toLongLong();
1645  const int64_t old_end_sample = md_obj->value(MetadataValueEndSample).toLongLong();
1646 
1647  if (start_sample != old_start_sample)
1648  md_obj->set_value(MetadataValueStartSample, QVariant((qlonglong)start_sample));
1649 
1650  if (end_sample != old_end_sample)
1651  md_obj->set_value(MetadataValueEndSample, QVariant((qlonglong)end_sample));
1652  }
1653 }
1654 
1656 {
1657  // Determine signal that the mouse cursor is hovering over
1659  if (hover_widget_ == this) {
1660  for (const shared_ptr<Signal>& s : signals_) {
1661  const pair<int, int> extents = s->v_extents();
1662  const int top = s->get_visual_y() + extents.first;
1663  const int btm = s->get_visual_y() + extents.second;
1664  if ((hover_point_.y() >= top) && (hover_point_.y() <= btm)
1665  && s->base()->enabled())
1667  }
1668  }
1669 
1670  // Update all trace tree items
1671  const vector<shared_ptr<TraceTreeItem>> trace_tree_items(
1672  list_by_type<TraceTreeItem>());
1673  for (const shared_ptr<TraceTreeItem>& r : trace_tree_items)
1674  r->hover_point_changed(hover_point_);
1675 
1676  // Notify this view's listeners
1678 
1679  // Hover point is -1 when invalid and 0 for the header
1680  if (hover_point_.x() > 0) {
1681  // Notify global listeners
1682  pv::util::Timestamp mouse_time = offset_ + hover_point_.x() * scale_;
1683  int64_t sample_num = (mouse_time * session_.get_samplerate()).convert_to<int64_t>();
1684 
1685  MetadataObject* md_obj =
1687  md_obj->set_value(MetadataValueStartSample, QVariant((qlonglong)sample_num));
1688  }
1689 }
1690 
1691 void View::row_item_appearance_changed(bool label, bool content)
1692 {
1693  if (label)
1694  header_->update();
1695  if (content)
1696  viewport_->update();
1697 }
1698 
1699 void View::time_item_appearance_changed(bool label, bool content)
1700 {
1701  if (label) {
1702  ruler_->update();
1703 
1704  // Make sure the header pane width is updated, too
1705  update_layout();
1706  }
1707 
1708  if (content)
1709  viewport_->update();
1710 }
1711 
1712 void View::extents_changed(bool horz, bool vert)
1713 {
1714  sticky_events_ |=
1715  (horz ? TraceTreeItemHExtentsChanged : 0) |
1716  (vert ? TraceTreeItemVExtentsChanged : 0);
1717 
1718  if (!lazy_event_handler_.isActive())
1719  lazy_event_handler_.start();
1720 }
1721 
1723 {
1724  if (!header_was_shrunk_)
1726 }
1727 
1729 {
1730  // The header can only shrink when the splitter is moved manually
1732 
1733  if (!header_was_shrunk_)
1735 }
1736 
1738 {
1739  zoom(1);
1740 }
1741 
1743 {
1744  zoom(-1);
1745 }
1746 
1748 {
1749  set_h_offset(0);
1750 }
1751 
1753 {
1755 }
1756 
1758 {
1759  if (updating_scroll_)
1760  return;
1761 
1762  // Disable sticky scrolling when user moves the horizontal scroll bar
1763  // during a running acquisition
1765  sticky_scrolling_ = false;
1766  sticky_scrolling_changed(false);
1767  }
1768 
1769  const int range = scrollarea_->horizontalScrollBar()->maximum();
1770  if (range < MaxScrollValue)
1771  set_offset(scale_ * value);
1772  else {
1773  double length = 0;
1774  Timestamp offset;
1775  get_scroll_layout(length, offset);
1776  set_offset(scale_ * length * value / MaxScrollValue);
1777  }
1778 
1779  ruler_->update();
1780  viewport_->update();
1781 }
1782 
1784 {
1785  header_->update();
1786  viewport_->update();
1787 }
1788 
1789 void View::on_grab_ruler(int ruler_id)
1790 {
1791  if (!cursors_shown()) {
1792  center_cursors();
1793  show_cursors();
1794  }
1795 
1796  // Release the grabbed widget if its trigger hotkey was pressed twice
1797  if (ruler_id == 1)
1798  grabbed_widget_ = (grabbed_widget_ == cursors_->first().get()) ?
1799  nullptr : cursors_->first().get();
1800  else
1801  grabbed_widget_ = (grabbed_widget_ == cursors_->second().get()) ?
1802  nullptr : cursors_->second().get();
1803 
1804  if (grabbed_widget_)
1806  mapFromGlobal(QCursor::pos()).x() - header_width()));
1807 }
1808 
1810 {
1811  using sigrok::Channel;
1812 
1813  lock_guard<mutex> lock(signal_mutex_);
1814 
1815  vector< shared_ptr<Channel> > channels;
1816  shared_ptr<sigrok::Device> sr_dev;
1817  bool signals_added_or_removed = false;
1818 
1819  // Do we need to set the vertical scrollbar to its default position later?
1820  // We do if there are no traces, i.e. the scroll bar has no range set
1821  bool reset_scrollbar =
1822  (scrollarea_->verticalScrollBar()->minimum() ==
1823  scrollarea_->verticalScrollBar()->maximum());
1824 
1825  if (!session_.device()) {
1826  reset_scroll();
1827  signals_.clear();
1828  } else {
1829  sr_dev = session_.device()->device();
1830  assert(sr_dev);
1831  channels = sr_dev->channels();
1832  }
1833 
1834  vector< shared_ptr<TraceTreeItem> > new_top_level_items;
1835 
1836  // Make a list of traces that are being added, and a list of traces
1837  // that are being removed. The set_difference() algorithms require
1838  // both sets to be in the exact same order, which means that PD signals
1839  // must always be last as they interrupt the sort order otherwise
1840  const vector<shared_ptr<Trace>> prev_trace_list = list_by_type<Trace>();
1841  const set<shared_ptr<Trace>> prev_traces(
1842  prev_trace_list.begin(), prev_trace_list.end());
1843 
1844  set< shared_ptr<Trace> > traces(signals_.begin(), signals_.end());
1845 
1846 #ifdef ENABLE_DECODE
1847  traces.insert(decode_traces_.begin(), decode_traces_.end());
1848 #endif
1849  set< shared_ptr<Trace> > add_traces;
1850  set_difference(traces.begin(), traces.end(),
1851  prev_traces.begin(), prev_traces.end(),
1852  inserter(add_traces, add_traces.begin()));
1853 
1854  set< shared_ptr<Trace> > remove_traces;
1855  set_difference(prev_traces.begin(), prev_traces.end(),
1856  traces.begin(), traces.end(),
1857  inserter(remove_traces, remove_traces.begin()));
1858 
1859  // Make a look-up table of sigrok Channels to pulseview Signals
1860  map<shared_ptr<data::SignalBase>, shared_ptr<Signal> > signal_map;
1861  for (const shared_ptr<Signal>& sig : signals_)
1862  signal_map[sig->base()] = sig;
1863 
1864  // Populate channel groups
1865  if (sr_dev)
1866  for (auto& entry : sr_dev->channel_groups()) {
1867  const shared_ptr<sigrok::ChannelGroup> &group = entry.second;
1868 
1869  if (group->channels().size() <= 1)
1870  continue;
1871 
1872  // Find best trace group to add to
1874  group, signal_map);
1875 
1876  // If there is no trace group, create one
1877  shared_ptr<TraceGroup> new_trace_group;
1878  if (!owner) {
1879  new_trace_group.reset(new TraceGroup());
1880  owner = new_trace_group.get();
1881  }
1882 
1883  // Extract traces for the trace group, removing them from
1884  // the add list
1885  const vector< shared_ptr<Trace> > new_traces_in_group =
1886  extract_new_traces_for_channels(group->channels(),
1887  signal_map, add_traces);
1888 
1889  // Add the traces to the group
1890  const pair<int, int> prev_v_extents = owner->v_extents();
1891  int offset = prev_v_extents.second - prev_v_extents.first;
1892  for (const shared_ptr<Trace>& trace : new_traces_in_group) {
1893  assert(trace);
1894  owner->add_child_item(trace);
1895 
1896  const pair<int, int> extents = trace->v_extents();
1897  if (trace->enabled())
1898  offset += -extents.first;
1899  trace->force_to_v_offset(offset);
1900  if (trace->enabled())
1901  offset += extents.second;
1902  }
1903 
1904  if (new_trace_group) {
1905  // Assign proper vertical offsets to each channel in the group
1906  new_trace_group->restack_items();
1907 
1908  // If this is a new group, enqueue it in the new top level
1909  // items list
1910  if (!new_traces_in_group.empty())
1911  new_top_level_items.push_back(new_trace_group);
1912  }
1913  }
1914 
1915  // Enqueue the remaining logic channels in a group
1916  vector< shared_ptr<Channel> > logic_channels;
1917  copy_if(channels.begin(), channels.end(), back_inserter(logic_channels),
1918  [](const shared_ptr<Channel>& c) {
1919  return c->type() == sigrok::ChannelType::LOGIC; });
1920 
1921  const vector< shared_ptr<Trace> > non_grouped_logic_signals =
1922  extract_new_traces_for_channels(logic_channels, signal_map, add_traces);
1923 
1924  if (non_grouped_logic_signals.size() > 0) {
1925  const shared_ptr<TraceGroup> non_grouped_trace_group(
1926  make_shared<TraceGroup>());
1927  for (const shared_ptr<Trace>& trace : non_grouped_logic_signals)
1928  non_grouped_trace_group->add_child_item(trace);
1929 
1930  non_grouped_trace_group->restack_items();
1931  new_top_level_items.push_back(non_grouped_trace_group);
1932  }
1933 
1934  // Enqueue the remaining channels as free ungrouped traces
1935  const vector< shared_ptr<Trace> > new_top_level_signals =
1936  extract_new_traces_for_channels(channels, signal_map, add_traces);
1937  new_top_level_items.insert(new_top_level_items.end(),
1938  new_top_level_signals.begin(), new_top_level_signals.end());
1939 
1940  // Enqueue any remaining traces i.e. decode traces
1941  new_top_level_items.insert(new_top_level_items.end(),
1942  add_traces.begin(), add_traces.end());
1943 
1944  // Remove any removed traces
1945  for (const shared_ptr<Trace>& trace : remove_traces) {
1946  TraceTreeItemOwner *const owner = trace->owner();
1947  assert(owner);
1948  owner->remove_child_item(trace);
1949  signals_added_or_removed = true;
1950  }
1951 
1952  // Remove any empty trace groups
1953  for (shared_ptr<TraceGroup> group : list_by_type<TraceGroup>())
1954  if (group->child_items().size() == 0) {
1955  remove_child_item(group);
1956  group.reset();
1957  }
1958 
1959  // Add and position the pending top levels items
1960  int offset = v_extents().second;
1961  for (shared_ptr<TraceTreeItem> item : new_top_level_items) {
1962  // items may already have gained an owner when they were added to a group above
1963  if (item->owner())
1964  continue;
1965 
1966  add_child_item(item);
1967 
1968  // Position the item after the last item or at the top if there is none
1969  const pair<int, int> extents = item->v_extents();
1970 
1971  if (item->enabled())
1972  offset += -extents.first;
1973 
1974  item->force_to_v_offset(offset);
1975 
1976  if (item->enabled())
1977  offset += extents.second;
1978  signals_added_or_removed = true;
1979  }
1980 
1981 
1982  if (signals_added_or_removed && !header_was_shrunk_)
1984 
1985  update_layout();
1986 
1987  header_->update();
1988  viewport_->update();
1989 
1990  if (reset_scrollbar)
1992 }
1993 
1995 {
1996  GlobalSettings settings;
1997 
1998  if (state == Session::Running) {
2000 
2001  trigger_markers_.clear();
2003  set_zero_position(0);
2004 
2007 
2008  // Activate "always zoom to fit" if the setting is enabled and we're
2009  // the main view of this session (other trace views may be used for
2010  // zooming and we don't want to mess them up)
2011  bool state = settings.value(GlobalSettings::Key_View_ZoomToFitDuringAcq).toBool();
2012  if (is_main_view_ && state && !restoring_state_) {
2013  always_zoom_to_fit_ = true;
2015  }
2016 
2017  // Enable sticky scrolling if the setting is enabled
2019  settings.value(GlobalSettings::Key_View_StickyScrolling).toBool();
2020 
2021  // Reset all traces to segment 0
2022  current_segment_ = 0;
2024  }
2025 
2026  if (state == Session::Stopped) {
2027  // After acquisition has stopped we need to re-calculate the ticks once
2028  // as it's otherwise done when the user pans or zooms, which is too late
2030 
2031  // Reset "always zoom to fit", the acquisition has stopped
2032  if (always_zoom_to_fit_) {
2033  // Perform a final zoom-to-fit before disabling
2035  always_zoom_to_fit_ = false;
2037  }
2038 
2039  bool zoom_to_fit_after_acq =
2040  settings.value(GlobalSettings::Key_View_ZoomToFitAfterAcq).toBool();
2041 
2042  // Only perform zoom-to-fit if the user hasn't altered the viewport and
2043  // we didn't restore settings in the meanwhile
2044  if (zoom_to_fit_after_acq &&
2045  !restoring_state_ &&
2046  (scale_ == scale_at_acq_start_) &&
2048  zoom_fit(false); // We're stopped, so the GUI state doesn't matter
2049  }
2050 
2051  restoring_state_ = false;
2052  }
2053 }
2054 
2055 void View::on_new_segment(int new_segment_id)
2056 {
2057  on_segment_changed(new_segment_id);
2058 }
2059 
2060 void View::on_segment_completed(int segment_id)
2061 {
2062  on_segment_changed(segment_id);
2063 }
2064 
2065 void View::on_segment_changed(int segment)
2066 {
2067  switch (segment_display_mode_) {
2070  set_current_segment(segment);
2071  break;
2072 
2074  // Only update if all segments are complete
2075  if (session_.all_segments_complete(segment))
2076  set_current_segment(segment);
2077  break;
2078 
2081  default:
2082  break;
2083  }
2084 }
2085 
2087 {
2088  const QPoint p = ruler_->mapFrom(this, QPoint(context_menu_x_pos_, 0));
2089 
2091 }
2092 
2094 {
2095  (void)new_value;
2096 
2099 }
2100 
2102 {
2103  if (always_zoom_to_fit_) {
2104  zoom_fit(true);
2105  } else if (sticky_scrolling_) {
2106  // Make right side of the view sticky
2107  double length = 0;
2108  Timestamp offset;
2109  get_scroll_layout(length, offset);
2110 
2111  const QSize areaSize = viewport_->size();
2112  length = max(length - areaSize.width(), 0.0);
2113 
2114  set_offset(scale_ * length);
2115  }
2116 
2118  update_scroll();
2119  ruler_->update();
2120  viewport_->update();
2121 }
2122 
2124 {
2126  update_layout();
2129  update_scroll();
2130  }
2131 
2132  // Clear the sticky events
2133  sticky_events_ = 0;
2134 }
2135 
2136 } // namespace trace
2137 } // namespace views
2138 } // namespace pv
Session & session()
Definition: view.cpp:320
manual pdf if(NOT EXISTS"${CMAKE_CURRENT_BINARY_DIR}/images") message(STATUS"creating symlink for manual's images/ subdirectory") execute_process(COMMAND $
Definition: CMakeLists.txt:42
Viewport * viewport()
Definition: view.cpp:476
void on_scroll_to_start_shortcut_triggered()
Definition: view.cpp:1747
pv::util::Timestamp tick_period_
Definition: view.hpp:547
const Ruler * ruler() const
Definition: view.cpp:491
View(Session &session, bool is_main_view=false, QMainWindow *parent=nullptr)
Definition: view.cpp:127
void adjust_top_margin()
Definition: view.cpp:1299
util::TimeUnit time_unit() const
Definition: view.cpp:730
virtual void remove_signalbase(const shared_ptr< data::SignalBase > signalbase)
Definition: view.cpp:396
int header_width() const
Definition: view.cpp:1175
pair< pv::util::Timestamp, pv::util::Timestamp > get_time_extents() const
Definition: view.cpp:928
void h_scroll_value_changed(int value)
Definition: view.cpp:1757
bool eventFilter(QObject *object, QEvent *event)
Definition: view.cpp:1518
shared_ptr< CursorPair > cursors() const
Definition: view.cpp:1007
TraceTreeItemOwner * find_prevalent_trace_group(const shared_ptr< sigrok::ChannelGroup > &group, const map< shared_ptr< data::SignalBase >, shared_ptr< Signal > > &signal_map)
Definition: view.cpp:1439
void determine_time_unit()
Definition: view.cpp:1499
bool colored_bg() const
Definition: view.cpp:963
QShortcut * zoom_out_shortcut_
Definition: view.hpp:510
pv::util::SIPrefix tick_prefix() const
Definition: view.cpp:686
vector< shared_ptr< Signal > > signals_
Definition: view.hpp:516
void hover_point_changed(const QWidget *widget, const QPoint &hp)
Definition: moc_view.cpp:506
vector< shared_ptr< TriggerMarker > > trigger_markers_
Definition: view.hpp:559
shared_ptr< Signal > signal_under_mouse_cursor_
Definition: view.hpp:564
void set_v_offset(int offset)
Definition: view.cpp:657
static const QString Key_View_ShowAnalogMinorGrid
SIPrefix
Definition: util.hpp:50
virtual View * view()
Definition: view.cpp:466
void set_time(const pv::util::Timestamp &time) override
Definition: timemarker.cpp:62
QShortcut * zoom_in_shortcut_
Definition: view.hpp:509
const bool is_main_view_
Definition: viewbase.hpp:125
virtual void perform_delayed_view_update()
Definition: view.cpp:2101
const pv::util::Timestamp & offset() const
Definition: view.cpp:605
void on_setting_changed(const QString &key, const QVariant &value)
Definition: view.cpp:1180
void set_zoom(double scale, int offset)
Definition: view.cpp:1219
double scale_
The view time scale in seconds per pixel.
Definition: view.hpp:528
void tick_period_changed()
Emitted when the tick_period changed.
Definition: moc_view.cpp:557
static const int ScaleUnits[3]
Definition: view.hpp:100
void remove_child_item(shared_ptr< TraceTreeItem > item)
void get_scroll_layout(double &length, pv::util::Timestamp &offset) const
Definition: view.cpp:1212
void set_time_unit(pv::util::TimeUnit time_unit)
Definition: view.cpp:735
void set_tick_prefix(pv::util::SIPrefix tick_prefix)
Definition: view.cpp:691
bool segment_selectable_
Signals whether the user can change the currently shown segment.
Definition: view.hpp:525
Session & session_
Definition: viewbase.hpp:123
bool viewportEvent(QEvent *event)
Definition: view.cpp:109
void on_segment_completed(int new_segment_id)
Definition: view.cpp:2060
static pv::util::Timestamp restore_timestamp(QSettings &settings, const char *name)
void row_item_appearance_changed(bool label, bool content)
Definition: view.cpp:1691
T value(details::expression_node< T > *n)
Definition: exprtk.hpp:12358
QShortcut * home_shortcut_
Definition: view.hpp:511
QSize sizeHint() const override
Definition: ruler.cpp:68
virtual void set_value(MetadataValueType value_type, const QVariant &value)
QShortcut * zoom_in_shortcut_2_
Definition: view.hpp:509
shared_ptr< ViewItem > get_mouse_over_item(const QPoint &pt)
Definition: viewport.cpp:70
MetadataObject * find_object_by_type(MetadataObjectType obj_type)
QShortcut * grab_ruler_right_shortcut_
Definition: view.hpp:512
void sticky_scrolling_changed(bool state)
Definition: moc_view.cpp:531
QAbstractScrollArea * scrollarea() const
Definition: view.cpp:486
bool custom_zero_offset_set_
Shows whether the user set a custom zero offset that we should keep.
Definition: view.hpp:537
void set_segment_display_mode(Trace::SegmentDisplayMode mode)
Definition: view.cpp:778
void set_tick_period(const pv::util::Timestamp &tick_period)
Definition: view.cpp:722
static const QString Key_View_SnapDistance
void set_offset(const pv::util::Timestamp &offset, bool force_update=false)
Definition: view.cpp:593
static const QString Key_View_TriggerIsZeroTime
void set_current_segment(uint32_t segment_id)
Definition: view.cpp:743
uint32_t current_segment_
The ID of the currently displayed segment.
Definition: viewbase.hpp:135
vector< shared_ptr< pv::data::SignalData > > get_visible_data() const
Definition: view.cpp:917
void set_tick_precision(unsigned tick_precision)
Definition: view.cpp:704
virtual void clear_signalbases()
Definition: viewbase.cpp:92
virtual void add_signalbase(const shared_ptr< data::SignalBase > signalbase)
Definition: view.cpp:354
MetadataObject * create_object(MetadataObjectType obj_type)
void scale_changed()
Emitted when the scale changed.
Definition: moc_view.cpp:525
void reset_zero_position()
Definition: view.cpp:625
virtual void focus_on_range(uint64_t start_sample, uint64_t end_sample)
Definition: view.cpp:859
void time_item_appearance_changed(bool label, bool content)
Definition: view.cpp:1699
void on_splitter_moved()
Definition: view.cpp:1728
int exponent(SIPrefix prefix)
Returns the exponent that corresponds to a given prefix.
Definition: util.cpp:68
QTimer lazy_event_handler_
Definition: view.hpp:568
unsigned int sticky_events_
Definition: view.hpp:567
MetadataObjManager * metadata_obj_manager()
Definition: session.cpp:1050
m
Definition: CMakeCache.txt:621
void resize_header_to_fit()
Definition: view.cpp:1411
unsigned int depth() const
Definition: view.cpp:676
void show_cursors(bool show=true)
Definition: view.cpp:973
TimeMarker * grabbed_widget_
Definition: view.hpp:562
bool is_main_view() const
Definition: viewbase.cpp:67
void tick_precision_changed()
Emitted when the tick_precision changed.
Definition: moc_view.cpp:551
const QWidget * hover_widget() const
Definition: view.cpp:1047
void add_child_item(shared_ptr< TraceTreeItem > item)
virtual void reset_view_state()
Definition: view.cpp:278
void process_sticky_events()
Definition: view.cpp:2123
virtual void contextMenuEvent(QContextMenuEvent *event)
Definition: view.cpp:1596
T max(const T v0, const T v1)
Definition: exprtk.hpp:1411
void segment_changed(int segment_id)
Emitted when the currently selected segment changed.
Definition: moc_view.cpp:569
uint32_t current_segment() const
Definition: view.cpp:681
shared_ptr< devices::Device > device() const
Definition: session.cpp:163
void time_unit_changed()
Emitted when the time_unit changed.
Definition: moc_view.cpp:563
pv::util::Timestamp zero_offset_
The offset of the zero point in seconds.
Definition: view.hpp:535
vector< shared_ptr< TimeItem > > time_items() const
Definition: view.cpp:560
static const QString Key_View_StickyScrolling
static void remove_change_handler(GlobalSettingsInterface *cb)
pv::util::Timestamp zero_offset() const
Definition: view.cpp:647
uint32_t get_highest_segment_id() const
Definition: session.cpp:933
void update_hover_point()
Definition: view.cpp:1655
void set_scale_offset(double scale, const pv::util::Timestamp &offset)
Definition: view.cpp:889
static std::string data()
Definition: exprtk.hpp:39024
void v_scroll_value_changed()
Definition: view.cpp:1783
void on_grab_ruler(int ruler_id)
Definition: view.cpp:1789
double get_samplerate() const
Definition: session.cpp:910
CustomScrollArea(QWidget *parent=nullptr)
Definition: view.cpp:104
list< shared_ptr< Flag > > flags_
Definition: view.hpp:556
void offset_changed()
Emitted when the offset changed.
Definition: moc_view.cpp:519
static void store_timestamp(QSettings &settings, const char *name, const pv::util::Timestamp &ts)
static const int MaxScrollValue
Definition: view.hpp:97
virtual void add_signalbase(const shared_ptr< data::SignalBase > signalbase)
Definition: viewbase.cpp:104
void update_view_range_metaobject() const
Definition: view.cpp:1630
void remove_flag(shared_ptr< Flag > flag)
Definition: view.cpp:1025
unsigned int minor_tick_count() const
Definition: view.cpp:717
QShortcut * zoom_out_shortcut_2_
Definition: view.hpp:510
virtual void remove_signalbase(const shared_ptr< data::SignalBase > signalbase)
Definition: viewbase.cpp:114
CustomScrollArea * scrollarea_
Definition: view.hpp:503
vector< shared_ptr< Signal > > signals() const
Definition: view.cpp:330
static const int ViewScrollMargin
Definition: view.hpp:98
T min(const T v0, const T v1)
Definition: exprtk.hpp:1404
Trace::SegmentDisplayMode segment_display_mode_
Definition: view.hpp:522
void calculate_tick_spacing()
Definition: view.cpp:1231
shared_ptr< views::ViewBase > main_view() const
Definition: session.cpp:201
capture_state get_capture_state() const
Definition: session.cpp:788
void cursor_state_changed(bool show)
Emitted when the cursors are shown/hidden.
Definition: moc_view.cpp:583
pv::util::SIPrefix tick_prefix_
Definition: view.hpp:548
uint32_t context_menu_x_pos_
Definition: view.hpp:581
void on_zoom_out_shortcut_triggered()
Definition: view.cpp:1742
shared_ptr< Signal > get_signal_by_signalbase(shared_ptr< data::SignalBase > base) const
Definition: view.cpp:335
unsigned int minor_tick_count_
Definition: view.hpp:549
pv::util::Timestamp ruler_offset_
The ruler version of the time offset in seconds.
Definition: view.hpp:533
void zoom_fit(bool gui_state)
Definition: view.cpp:834
virtual void reset_view_state()
Definition: viewbase.cpp:72
virtual unsigned int depth() const =0
unsigned int tick_precision_
Definition: view.hpp:550
void restack_all_trace_tree_items()
Definition: view.cpp:1153
bool cursors_shown() const
Definition: view.cpp:968
virtual void clear_signalbases()
Definition: view.cpp:348
vector< util::Timestamp > get_triggers(uint32_t segment_id) const
Definition: session.cpp:946
vector< shared_ptr< Flag > > flags() const
Definition: view.cpp:1031
QShortcut * cancel_grab_shortcut_
Definition: view.hpp:513
#define countof(x)
Definition: extdef.h:23
Trace::SegmentDisplayMode segment_display_mode() const
Definition: view.cpp:773
static QString format_time_with_distance(const pv::util::Timestamp &distance, const pv::util::Timestamp &t, pv::util::SIPrefix prefix=pv::util::SIPrefix::unspecified, pv::util::TimeUnit unit=pv::util::TimeUnit::Time, unsigned precision=0, bool sign=true)
Definition: ruler.cpp:84
virtual QVariant value(MetadataValueType value_type) const
void on_zoom_in_shortcut_triggered()
Definition: view.cpp:1737
const QPoint & hover_point() const
Definition: view.cpp:1042
virtual void save_settings(QSettings &settings) const
Definition: view.cpp:496
int get_h_scrollbar_maximum() const
Definition: view.cpp:671
static const QString Key_View_ZoomToFitAfterAcq
void on_create_marker_here()
Definition: view.cpp:2086
void on_new_segment(int new_segment_id)
Definition: view.cpp:2055
static const QString Key_View_ShowSamplingPoints
virtual void restore_settings(QSettings &settings)
Definition: view.cpp:519
static const QString Key_View_ZoomToFitDuringAcq
x y t t *t x y t t t x y t t t x *y t *t t x *y t *t t x y t t t x y t t t x(y+z)
shared_ptr< Signal > get_signal_under_mouse_cursor() const
Definition: view.cpp:461
void tick_prefix_changed()
Emitted when the tick_prefix changed.
Definition: moc_view.cpp:545
int owner_visual_v_offset() const
Definition: view.cpp:652
manual txt set(MANUAL_OUT_HTML"${CMAKE_CURRENT_BINARY_DIR}/manual.html") set(MANUAL_OUT_PDF"$
Definition: CMakeLists.txt:36
pv::util::Timestamp offset_at_acq_start_
Definition: view.hpp:578
void trigger_event(int segment_id, util::Timestamp location)
Definition: view.cpp:1200
void on_segment_changed(int segment)
Definition: view.cpp:2065
void on_scroll_to_end_shortcut_triggered()
Definition: view.cpp:1752
pv::util::Timestamp get_absolute_time_from_x_pos(uint32_t x) const
Definition: ruler.cpp:121
bool scroll_needs_defaults_
Definition: view.hpp:571
static const int MaxViewAutoUpdateRate
Definition: viewbase.hpp:71
TimeUnit
Definition: util.hpp:44
QWidget * hover_widget_
Definition: view.hpp:561
const pv::util::Timestamp & tick_period() const
Definition: view.cpp:712
QSize extended_size_hint() const
Definition: header.cpp:75
boost::multiprecision::number< boost::multiprecision::cpp_dec_float< 24 >, boost::multiprecision::et_off > Timestamp
Timestamp type providing yoctosecond resolution.
Definition: util.hpp:67
void zoom(double steps)
Definition: view.cpp:824
static const pv::util::Timestamp MaxScale
Definition: view.hpp:94
void always_zoom_to_fit_changed(bool state)
Definition: moc_view.cpp:538
const pv::util::Timestamp & ruler_offset() const
Definition: view.cpp:610
void segment_display_mode_changed(int mode, bool segment_selectable)
Definition: moc_view.cpp:576
void set_scale(double scale)
Definition: view.cpp:582
pv::util::Timestamp offset_
The internal view version of the time offset in seconds.
Definition: view.hpp:531
QShortcut * grab_ruler_left_shortcut_
Definition: view.hpp:512
T pow(const T v0, const T v1)
Definition: exprtk.hpp:1439
virtual ViewType get_type() const
Definition: view.cpp:273
void set_h_offset(int offset)
Definition: view.cpp:664
void on_signal_name_changed()
Definition: view.cpp:1722
void set_cursors(pv::util::Timestamp &first, pv::util::Timestamp &second)
Definition: view.cpp:984
util::TimeUnit time_unit_
Definition: view.hpp:551
shared_ptr< CursorPair > cursors_
Definition: view.hpp:554
QSplitter * splitter_
Definition: view.hpp:507
static vector< shared_ptr< Trace > > extract_new_traces_for_channels(const vector< shared_ptr< sigrok::Channel > > &channels, const map< shared_ptr< data::SignalBase >, shared_ptr< Signal > > &signal_map, set< shared_ptr< Trace > > &add_list)
Definition: view.cpp:1475
void remove_trace(shared_ptr< Trace > trace)
Definition: view.cpp:440
void set_zero_position(const pv::util::Timestamp &position)
Definition: view.cpp:615
void set_scroll_default()
Definition: view.cpp:1381
int64_t get_nearest_level_change(const QPoint &p)
Definition: view.cpp:1052
void resizeEvent(QResizeEvent *event)
Definition: view.cpp:1621
bool all_segments_complete(uint32_t segment_id) const
Definition: session.cpp:1039
unsigned int tick_precision() const
Definition: view.cpp:699
Viewport * viewport_
Definition: view.hpp:504
uint16_t snap_distance_
Definition: view.hpp:565
double scale_at_acq_start_
Definition: view.hpp:577
QShortcut * end_shortcut_
Definition: view.hpp:511
bool segment_is_selectable() const
Definition: view.cpp:768
static void add_change_handler(GlobalSettingsInterface *cb)
double scale() const
Definition: view.cpp:577
static const QString Key_View_ColoredBG
static const pv::util::Timestamp MinScale
Definition: view.hpp:95
void determine_if_header_was_shrunk()
Definition: view.cpp:1400
shared_ptr< Flag > add_flag(const pv::util::Timestamp &time)
Definition: view.cpp:1012
void on_settingViewTriggerIsZeroTime_changed(const QVariant new_value)
Definition: view.cpp:2093
void capture_state_updated(int state)
Definition: view.cpp:1994
void extents_changed(bool horz, bool vert)
Definition: view.cpp:1712