using std::back_inserter;
using std::copy_if;
using std::count_if;
-using std::dynamic_pointer_cast;
using std::inserter;
using std::max;
using std::make_pair;
View::View(Session &session, bool is_main_view, QWidget *parent) :
ViewBase(session, is_main_view, parent),
splitter_(new QSplitter()),
+ segment_display_mode_(Trace::ShowLastSegmentOnly),
+ segment_selectable_(false),
scale_(1e-3),
offset_(0),
+ ruler_offset_(0),
updating_scroll_(false),
settings_restored_(false),
+ header_was_shrunk_(false),
sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui()
always_zoom_to_fit_(false),
tick_period_(0),
trigger_markers_(),
hover_point_(-1, -1),
scroll_needs_defaults_(true),
- saved_v_offset_(0)
+ saved_v_offset_(0),
+ scale_at_acq_start_(0),
+ offset_at_acq_start_(0),
+ suppress_zoom_to_fit_after_acq_(false)
{
QVBoxLayout *root_layout = new QVBoxLayout(this);
root_layout->setContentsMargins(0, 0, 0, 0);
splitter_->setHandleWidth(1); // Don't show a visible rubber band
splitter_->setCollapsible(0, false); // Prevent the header from collapsing
splitter_->setCollapsible(1, false); // Prevent the traces from collapsing
+ splitter_->setStretchFactor(0, 0); // Prevent the panes from being resized
+ splitter_->setStretchFactor(1, 1); // when the entire view is resized
splitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
viewport_->installEventFilter(this);
// Set up settings and event handlers
GlobalSettings settings;
- coloured_bg_ = settings.value(GlobalSettings::Key_View_ColouredBG).toBool();
+ colored_bg_ = settings.value(GlobalSettings::Key_View_ColoredBG).toBool();
+
+ GlobalSettings::add_change_handler(this);
connect(scrollarea_->horizontalScrollBar(), SIGNAL(valueChanged(int)),
this, SLOT(h_scroll_value_changed(int)));
connect(splitter_, SIGNAL(splitterMoved(int, int)),
this, SLOT(on_splitter_moved()));
- connect(this, SIGNAL(hover_point_changed()),
- this, SLOT(on_hover_point_changed()));
-
connect(&lazy_event_handler_, SIGNAL(timeout()),
this, SLOT(process_sticky_events()));
lazy_event_handler_.setSingleShot(true);
// Update the zoom state
calculate_tick_spacing();
+
+ // Make sure the standard bar's segment selector is in sync
+ set_segment_display_mode(segment_display_mode_);
+}
+
+View::~View()
+{
+ GlobalSettings::remove_change_handler(this);
}
Session& View::session()
{
ViewBase::add_signalbase(signal->base());
signals_.insert(signal);
+
+ signal->set_segment_display_mode(segment_display_mode_);
+ signal->set_current_segment(current_segment_);
+
+ connect(signal->base().get(), SIGNAL(name_changed(const QString&)),
+ this, SLOT(on_signal_name_changed()));
}
#ifdef ENABLE_DECODE
decode_traces_.clear();
}
-void View::add_decode_signal(shared_ptr<data::SignalBase> signalbase)
+void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
{
shared_ptr<DecodeTrace> d(
- new DecodeTrace(session_, signalbase, decode_traces_.size()));
+ new DecodeTrace(session_, signal, decode_traces_.size()));
decode_traces_.push_back(d);
+
+ d->set_segment_display_mode(segment_display_mode_);
+ d->set_current_segment(current_segment_);
+
+ connect(signal.get(), SIGNAL(name_changed(const QString&)),
+ this, SLOT(on_signal_name_changed()));
}
-void View::remove_decode_signal(shared_ptr<data::SignalBase> signalbase)
+void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
{
for (auto i = decode_traces_.begin(); i != decode_traces_.end(); i++)
- if ((*i)->base() == signalbase) {
+ if ((*i)->base() == signal) {
decode_traces_.erase(i);
signals_changed();
return;
scrollarea_->verticalScrollBar()->sliderPosition());
settings.setValue("splitter_state", splitter_->saveState());
+ settings.setValue("segment_display_mode", segment_display_mode_);
- stringstream ss;
- boost::archive::text_oarchive oa(ss);
- oa << boost::serialization::make_nvp("offset", offset_);
- settings.setValue("offset", QString::fromStdString(ss.str()));
+ {
+ stringstream ss;
+ boost::archive::text_oarchive oa(ss);
+ oa << boost::serialization::make_nvp("ruler_shift", ruler_shift_);
+ settings.setValue("ruler_shift", QString::fromStdString(ss.str()));
+ }
+ {
+ stringstream ss;
+ boost::archive::text_oarchive oa(ss);
+ oa << boost::serialization::make_nvp("offset", offset_);
+ settings.setValue("offset", QString::fromStdString(ss.str()));
+ }
for (shared_ptr<Signal> signal : signals_) {
settings.beginGroup(signal->base()->internal_name());
if (settings.contains("scale"))
set_scale(settings.value("scale").toDouble());
+ if (settings.contains("ruler_shift")) {
+ util::Timestamp shift;
+ stringstream ss;
+ ss << settings.value("ruler_shift").toString().toStdString();
+
+ boost::archive::text_iarchive ia(ss);
+ ia >> boost::serialization::make_nvp("ruler_shift", shift);
+
+ ruler_shift_ = shift;
+ }
+
if (settings.contains("offset")) {
util::Timestamp offset;
stringstream ss;
boost::archive::text_iarchive ia(ss);
ia >> boost::serialization::make_nvp("offset", offset);
+ // This also updates ruler_offset_
set_offset(offset);
}
if (settings.contains("splitter_state"))
splitter_->restoreState(settings.value("splitter_state").toByteArray());
+ if (settings.contains("segment_display_mode"))
+ set_segment_display_mode(
+ (Trace::SegmentDisplayMode)(settings.value("segment_display_mode").toInt()));
+
for (shared_ptr<Signal> signal : signals_) {
settings.beginGroup(signal->base()->internal_name());
signal->restore_settings(settings);
}
settings_restored_ = true;
+ suppress_zoom_to_fit_after_acq_ = true;
+
+ // Update the ruler so that it uses the new scale
+ calculate_tick_spacing();
}
vector< shared_ptr<TimeItem> > View::time_items() const
}
}
+void View::set_offset(const pv::util::Timestamp& offset, bool force_update)
+{
+ if ((offset_ != offset) || force_update) {
+ offset_ = offset;
+ ruler_offset_ = offset_ + ruler_shift_;
+ offset_changed();
+ }
+}
+
const Timestamp& View::offset() const
{
return offset_;
}
-void View::set_offset(const pv::util::Timestamp& offset)
+const Timestamp& View::ruler_offset() const
{
- if (offset_ != offset) {
- offset_ = offset;
- offset_changed();
- }
+ return ruler_offset_;
+}
+
+void View::set_zero_position(pv::util::Timestamp& position)
+{
+ ruler_shift_ = -position;
+
+ // Force an immediate update of the offsets
+ set_offset(offset_, true);
+ ruler_->update();
+}
+
+void View::reset_zero_position()
+{
+ ruler_shift_ = 0;
+
+ // Force an immediate update of the offsets
+ set_offset(offset_, true);
+ ruler_->update();
}
int View::owner_visual_v_offset() const
return 0;
}
+uint32_t View::current_segment() const
+{
+ return current_segment_;
+}
+
pv::util::SIPrefix View::tick_prefix() const
{
return tick_prefix_;
return tick_period_;
}
+unsigned int View::minor_tick_count() const
+{
+ return minor_tick_count_;
+}
+
void View::set_tick_period(const pv::util::Timestamp& tick_period)
{
if (tick_period_ != tick_period) {
}
}
+void View::set_current_segment(uint32_t segment_id)
+{
+ current_segment_ = segment_id;
+
+ for (shared_ptr<Signal> signal : signals_)
+ signal->set_current_segment(current_segment_);
+#ifdef ENABLE_DECODE
+ for (shared_ptr<DecodeTrace> dt : decode_traces_)
+ dt->set_current_segment(current_segment_);
+#endif
+
+ vector<util::Timestamp> triggers = session_.get_triggers(current_segment_);
+
+ trigger_markers_.clear();
+ for (util::Timestamp timestamp : triggers)
+ trigger_markers_.push_back(make_shared<TriggerMarker>(*this, timestamp));
+
+ // When enabled, the first trigger for this segment is used as the zero position
+ GlobalSettings settings;
+ bool trigger_is_zero_time = settings.value(GlobalSettings::Key_View_TriggerIsZeroTime).toBool();
+
+ if (trigger_is_zero_time && (triggers.size() > 0))
+ set_zero_position(triggers.front());
+
+ viewport_->update();
+
+ segment_changed(segment_id);
+}
+
+bool View::segment_is_selectable() const
+{
+ return segment_selectable_;
+}
+
+Trace::SegmentDisplayMode View::segment_display_mode() const
+{
+ return segment_display_mode_;
+}
+
+void View::set_segment_display_mode(Trace::SegmentDisplayMode mode)
+{
+ trigger_markers_.clear();
+
+ segment_display_mode_ = mode;
+
+ for (shared_ptr<Signal> signal : signals_)
+ signal->set_segment_display_mode(mode);
+
+ uint32_t last_segment = session_.get_segment_count() - 1;
+
+ switch (mode) {
+ case Trace::ShowLastSegmentOnly:
+ if (current_segment_ != last_segment)
+ set_current_segment(last_segment);
+ break;
+
+ case Trace::ShowLastCompleteSegmentOnly:
+ // Do nothing if we only have one segment so far
+ if (last_segment > 0) {
+ // If the last segment isn't complete, the previous one must be
+ uint32_t segment_id =
+ (session_.all_segments_complete(last_segment)) ?
+ last_segment : last_segment - 1;
+
+ if (current_segment_ != segment_id)
+ set_current_segment(segment_id);
+ }
+ break;
+
+ case Trace::ShowSingleSegmentOnly:
+ case Trace::ShowAllSegments:
+ case Trace::ShowAccumulatedIntensity:
+ default:
+ // Current segment remains as-is
+ break;
+ }
+
+ segment_selectable_ = true;
+
+ if ((mode == Trace::ShowAllSegments) || (mode == Trace::ShowAccumulatedIntensity))
+ segment_selectable_ = false;
+
+ viewport_->update();
+
+ segment_display_mode_changed((int)mode, segment_selectable_);
+}
+
void View::zoom(double steps)
{
zoom(steps, viewport_->width() / 2);
viewport_->update();
}
-void View::enable_coloured_bg(bool state)
+void View::enable_colored_bg(bool state)
{
- coloured_bg_ = state;
+ colored_bg_ = state;
viewport_->update();
}
-bool View::coloured_bg() const
+bool View::colored_bg() const
{
- return coloured_bg_;
+ return colored_bg_;
}
bool View::cursors_shown() const
i->animate_to_layout_v_offset();
}
-void View::trigger_event(util::Timestamp location)
+void View::on_setting_changed(const QString &key, const QVariant &value)
{
+ if (key == GlobalSettings::Key_View_TriggerIsZeroTime)
+ on_settingViewTriggerIsZeroTime_changed(value);
+}
+
+void View::trigger_event(int segment_id, util::Timestamp location)
+{
+ // TODO This doesn't work if we're showing multiple segments at once
+ if ((uint32_t)segment_id != current_segment_)
+ return;
+
+ // Set zero location if the Key_View_TriggerIsZeroTime setting is set and
+ // if this is the first trigger for this segment.
+ GlobalSettings settings;
+ bool trigger_is_zero_time = settings.value(GlobalSettings::Key_View_TriggerIsZeroTime).toBool();
+
+ size_t trigger_count = session_.get_triggers(current_segment_).size();
+
+ if (trigger_is_zero_time && trigger_count == 1)
+ set_zero_position(location);
+
trigger_markers_.push_back(make_shared<TriggerMarker>(*this, location));
}
(ScaleUnits[unit++] + tp_margin);
} while (tp_with_margin < min_period && unit < countof(ScaleUnits));
+ minor_tick_count_ = (unit == 2) ? 4 : 5;
tick_period = order_decimal * ScaleUnits[unit - 1];
tick_prefix = static_cast<pv::util::SIPrefix>(
(order - pv::util::exponent(pv::util::SIPrefix::yocto)) / 3);
set_v_offset(extents.first);
}
-bool View::header_was_shrunk() const
+void View::determine_if_header_was_shrunk()
{
const int header_pane_width = splitter_->sizes().front();
const int header_width = header_->extended_size_hint().width();
// Allow for a slight margin of error so that we also accept
// slight differences when e.g. a label name change increased
// the overall width
- return (header_pane_width < (header_width - 10));
+ header_was_shrunk_ = (header_pane_width < (header_width - 10));
}
-void View::expand_header_to_fit()
+void View::resize_header_to_fit()
{
+ // Setting the maximum width of the header widget doesn't work as
+ // expected because the splitter would allow the user to make the
+ // pane wider than that, creating empty space as a result.
+ // To make this work, we stricly enforce the maximum width by
+ // expanding the header unless the user shrunk it on purpose.
+ // As we're then setting the width of the header pane, we set the
+ // splitter to the maximum allowed position.
+
int splitter_area_width = 0;
for (int w : splitter_->sizes())
splitter_area_width += w;
else
hover_point_ = QPoint(-1, -1);
- hover_point_changed();
+ update_hover_point();
} else if (type == QEvent::Leave) {
hover_point_ = QPoint(-1, -1);
- hover_point_changed();
+ update_hover_point();
} else if (type == QEvent::Show) {
// This is somewhat of a hack, unfortunately. We cannot use
// resized to their final sizes.
update_layout();
- if (!settings_restored_)
- expand_header_to_fit();
+ if (settings_restored_)
+ determine_if_header_was_shrunk();
+ else
+ resize_header_to_fit();
if (scroll_needs_defaults_) {
set_scroll_default();
update_layout();
}
+void View::update_hover_point()
+{
+ const vector<shared_ptr<TraceTreeItem>> trace_tree_items(
+ list_by_type<TraceTreeItem>());
+ for (shared_ptr<TraceTreeItem> r : trace_tree_items)
+ r->hover_point_changed(hover_point_);
+
+ hover_point_changed(hover_point_);
+}
+
void View::row_item_appearance_changed(bool label, bool content)
{
if (label)
lazy_event_handler_.start();
}
+void View::on_signal_name_changed()
+{
+ if (!header_was_shrunk_)
+ resize_header_to_fit();
+}
+
void View::on_splitter_moved()
{
- // Setting the maximum width of the header widget doesn't work as
- // expected because the splitter would allow the user to make the
- // pane wider than that, creating empty space as a result.
- // To make this work, we stricly enforce the maximum width by
- // expanding the header unless the user shrunk it on purpose.
- // As we're then setting the width of the header pane, we set the
- // splitter to the maximum allowed position.
- if (!header_was_shrunk())
- expand_header_to_fit();
+ // The header can only shrink when the splitter is moved manually
+ determine_if_header_was_shrunk();
+
+ if (!header_was_shrunk_)
+ resize_header_to_fit();
}
void View::h_scroll_value_changed(int value)
vector< shared_ptr<Channel> > channels;
shared_ptr<sigrok::Device> sr_dev;
+ bool signals_added_or_removed = false;
// Do we need to set the vertical scrollbar to its default position later?
// We do if there are no traces, i.e. the scroll bar has no range set
TraceTreeItemOwner *const owner = trace->owner();
assert(owner);
owner->remove_child_item(trace);
+ signals_added_or_removed = true;
}
// Remove any empty trace groups
if (item->enabled())
offset += extents.second;
+ signals_added_or_removed = true;
}
- if (!new_top_level_items.empty())
- // Expand the header pane because the header should become fully
- // visible when new signals are added
- expand_header_to_fit();
+ if (signals_added_or_removed && !header_was_shrunk_)
+ resize_header_to_fit();
update_layout();
void View::capture_state_updated(int state)
{
+ GlobalSettings settings;
+
if (state == Session::Running) {
set_time_unit(util::TimeUnit::Samples);
trigger_markers_.clear();
+ scale_at_acq_start_ = scale_;
+ offset_at_acq_start_ = offset_;
+
// Activate "always zoom to fit" if the setting is enabled and we're
// the main view of this session (other trace views may be used for
// zooming and we don't want to mess them up)
- GlobalSettings settings;
- bool state = settings.value(GlobalSettings::Key_View_AlwaysZoomToFit).toBool();
+ bool state = settings.value(GlobalSettings::Key_View_ZoomToFitDuringAcq).toBool();
if (is_main_view_ && state) {
always_zoom_to_fit_ = true;
always_zoom_to_fit_changed(always_zoom_to_fit_);
// Enable sticky scrolling if the setting is enabled
sticky_scrolling_ = settings.value(GlobalSettings::Key_View_StickyScrolling).toBool();
+
+ // Reset all traces to segment 0
+ current_segment_ = 0;
+ set_current_segment(current_segment_);
}
if (state == Session::Stopped) {
always_zoom_to_fit_ = false;
always_zoom_to_fit_changed(always_zoom_to_fit_);
}
+
+ bool zoom_to_fit_after_acq =
+ settings.value(GlobalSettings::Key_View_ZoomToFitAfterAcq).toBool();
+
+ // Only perform zoom-to-fit if the user hasn't altered the viewport and
+ // we didn't restore settings in the meanwhile
+ if (zoom_to_fit_after_acq &&
+ !suppress_zoom_to_fit_after_acq_ &&
+ (scale_ == scale_at_acq_start_) &&
+ (offset_ == offset_at_acq_start_))
+ zoom_fit(false); // We're stopped, so the GUI state doesn't matter
+
+ suppress_zoom_to_fit_after_acq_ = false;
}
}
+void View::on_new_segment(int new_segment_id)
+{
+ on_segment_changed(new_segment_id);
+}
+
+void View::on_segment_completed(int segment_id)
+{
+ on_segment_changed(segment_id);
+}
+
+void View::on_segment_changed(int segment)
+{
+ switch (segment_display_mode_) {
+ case Trace::ShowLastSegmentOnly:
+ case Trace::ShowSingleSegmentOnly:
+ set_current_segment(segment);
+ break;
+
+ case Trace::ShowLastCompleteSegmentOnly:
+ // Only update if all segments are complete
+ if (session_.all_segments_complete(segment))
+ set_current_segment(segment);
+ break;
+
+ case Trace::ShowAllSegments:
+ case Trace::ShowAccumulatedIntensity:
+ default:
+ break;
+ }
+}
+
+void View::on_settingViewTriggerIsZeroTime_changed(const QVariant new_value)
+{
+ if (new_value.toBool()) {
+ // The first trigger for this segment is used as the zero position
+ vector<util::Timestamp> triggers = session_.get_triggers(current_segment_);
+ if (triggers.size() > 0)
+ set_zero_position(triggers.front());
+ } else
+ reset_zero_position();
+}
+
void View::perform_delayed_view_update()
{
if (always_zoom_to_fit_) {
sticky_events_ = 0;
}
-void View::on_hover_point_changed()
-{
- const vector<shared_ptr<TraceTreeItem>> trace_tree_items(
- list_by_type<TraceTreeItem>());
- for (shared_ptr<TraceTreeItem> r : trace_tree_items)
- r->hover_point_changed();
-}
-
} // namespace trace
} // namespace views
} // namespace pv