#include <QApplication>
#include <QEvent>
#include <QFontMetrics>
+#include <QMenu>
#include <QMouseEvent>
#include <QScrollBar>
#include <QVBoxLayout>
View::View(Session &session, bool is_main_view, QWidget *parent) :
ViewBase(session, is_main_view, parent),
+
+ // Note: Place defaults in View::reset_view_state(), not here
splitter_(new QSplitter()),
- segment_display_mode_(Trace::ShowLastSegmentOnly),
- segment_selectable_(false),
- scale_(1e-3),
- 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),
- tick_prefix_(pv::util::SIPrefix::yocto),
- tick_precision_(0),
- time_unit_(util::TimeUnit::Time),
- show_cursors_(false),
- cursors_(new CursorPair(*this)),
- next_flag_text_('A'),
- trigger_markers_(),
- hover_point_(-1, -1),
- scroll_needs_defaults_(true),
- saved_v_offset_(0),
- scale_at_acq_start_(0),
- offset_at_acq_start_(0),
- suppress_zoom_to_fit_after_acq_(false)
+ header_was_shrunk_(false), // The splitter remains unchanged after a reset, so this goes here
+ sticky_scrolling_(false) // Default setting is set in MainWindow::setup_ui()
{
QVBoxLayout *root_layout = new QVBoxLayout(this);
root_layout->setContentsMargins(0, 0, 0, 0);
// 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)));
ruler_->raise();
header_->raise();
+ reset_view_state();
+}
+
+View::~View()
+{
+ GlobalSettings::remove_change_handler(this);
+}
+
+void View::reset_view_state()
+{
+ ViewBase::reset_view_state();
+
+ segment_display_mode_ = Trace::ShowLastSegmentOnly;
+ segment_selectable_ = false;
+ scale_ = 1e-3;
+ offset_ = 0;
+ ruler_offset_ = 0;
+ updating_scroll_ = false;
+ settings_restored_ = false;
+ always_zoom_to_fit_ = false;
+ tick_period_ = 0;
+ tick_prefix_ = pv::util::SIPrefix::yocto;
+ tick_precision_ = 0;
+ time_unit_ = util::TimeUnit::Time;
+ show_cursors_ = false;
+ cursors_ = make_shared<CursorPair>(*this);
+ next_flag_text_ = 'A';
+ trigger_markers_.clear();
+ hover_point_ = QPoint(-1, -1);
+ scroll_needs_defaults_ = true;
+ saved_v_offset_ = 0;
+ scale_at_acq_start_ = 0;
+ offset_at_acq_start_ = 0;
+ suppress_zoom_to_fit_after_acq_ = false;
+
+ show_cursors_ = false;
+ cursor_state_changed(show_cursors_);
+ flags_.clear();
+
// 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_);
}
Session& View::session()
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()));
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()));
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();
+
+ try {
+ boost::archive::text_iarchive ia(ss);
+ ia >> boost::serialization::make_nvp("ruler_shift", shift);
+ ruler_shift_ = shift;
+ } catch (boost::archive::archive_exception&) {
+ qDebug() << "Could not restore the view ruler shift";
+ }
+ }
+
if (settings.contains("offset")) {
util::Timestamp offset;
stringstream ss;
ss << settings.value("offset").toString().toStdString();
- boost::archive::text_iarchive ia(ss);
- ia >> boost::serialization::make_nvp("offset", offset);
-
- set_offset(offset);
+ try {
+ boost::archive::text_iarchive ia(ss);
+ ia >> boost::serialization::make_nvp("offset", offset);
+ // This also updates ruler_offset_
+ set_offset(offset);
+ } catch (boost::archive::archive_exception&) {
+ qDebug() << "Could not restore the view 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);
{
const vector<shared_ptr<Flag>> f(flags());
vector<shared_ptr<TimeItem>> items(f.begin(), f.end());
- items.push_back(cursors_);
- items.push_back(cursors_->first());
- items.push_back(cursors_->second());
+
+ if (cursors_) {
+ items.push_back(cursors_);
+ items.push_back(cursors_->first());
+ items.push_back(cursors_->second());
+ }
for (auto trigger_marker : trigger_markers_)
items.push_back(trigger_marker);
}
}
+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)
{
+ segment_display_mode_ = mode;
+
for (shared_ptr<Signal> signal : signals_)
signal->set_segment_display_mode(mode);
- viewport_->update();
+ 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::ShowSingleSegmentOnly)
+ if ((mode == Trace::ShowAllSegments) || (mode == Trace::ShowAccumulatedIntensity))
segment_selectable_ = false;
- segment_display_mode_changed(segment_selectable_);
+ viewport_->update();
+
+ segment_display_mode_changed((int)mode, segment_selectable_);
}
void View::zoom(double steps)
set_scale_offset(scale.convert_to<double>(), extents.first);
}
-void View::zoom_one_to_one()
-{
- using pv::data::SignalData;
-
- // Make a set of all the visible data objects
- set< shared_ptr<SignalData> > visible_data = get_visible_data();
- if (visible_data.empty())
- return;
-
- assert(viewport_);
- const int w = viewport_->width();
- if (w <= 0)
- return;
-
- set_zoom(1.0 / session_.get_samplerate(), w / 2);
-}
-
void View::set_scale_offset(double scale, const Timestamp& offset)
{
// Disable sticky scrolling / always zoom to fit when acquisition runs
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
void View::show_cursors(bool show)
{
show_cursors_ = show;
+ cursor_state_changed(show);
ruler_->update();
viewport_->update();
}
void View::centre_cursors()
{
- const double time_width = scale_ * viewport_->width();
- cursors_->first()->set_time(offset_ + time_width * 0.4);
- cursors_->second()->set_time(offset_ + time_width * 0.6);
- ruler_->update();
- viewport_->update();
+ if (cursors_) {
+ const double time_width = scale_ * viewport_->width();
+ cursors_->first()->set_time(offset_ + time_width * 0.4);
+ cursors_->second()->set_time(offset_ + time_width * 0.6);
+
+ ruler_->update();
+ viewport_->update();
+ }
}
shared_ptr<CursorPair> View::cursors() const
i->animate_to_layout_v_offset();
}
-void View::trigger_event(util::Timestamp location)
+int View::header_width() const
+{
+ return header_->extended_size_hint().width();
+}
+
+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);
vscrollbar->setRange(extents.first - areaSize.height(),
extents.second);
- if (scroll_needs_defaults_)
+ if (scroll_needs_defaults_) {
set_scroll_default();
+ scroll_needs_defaults_ = false;
+ }
}
void View::reset_scroll()
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
- header_was_shrunk_ = (header_pane_width < (header_width - 10));
+ header_was_shrunk_ = (header_pane_width < (header_width() - 10));
}
void View::resize_header_to_fit()
return QObject::eventFilter(object, event);
}
+void View::contextMenuEvent(QContextMenuEvent *event)
+{
+ QPoint pos = event->pos() - QPoint(0, ruler_->sizeHint().height());
+
+ const shared_ptr<ViewItem> r = viewport_->get_mouse_over_item(pos);
+ if (!r)
+ return;
+
+ QMenu *menu = r->create_view_context_menu(this, pos);
+ if (menu)
+ menu->exec(event->globalPos());
+}
+
void View::resizeEvent(QResizeEvent* event)
{
// Only adjust the top margin if we shrunk vertically
(horz ? TraceTreeItemHExtentsChanged : 0) |
(vert ? TraceTreeItemVExtentsChanged : 0);
+ lazy_event_handler_.stop();
lazy_event_handler_.start();
}
// 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) {
void View::on_new_segment(int new_segment_id)
{
on_segment_changed(new_segment_id);
- 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:
- current_segment_ = segment - 1;
- for (shared_ptr<Signal> signal : signals_)
- signal->set_current_segment(current_segment_);
- viewport_->update();
+ 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:
}
}
+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_) {