]> sigrok.org Git - pulseview.git/blobdiff - pv/views/trace/view.cpp
TraceView: Don't use unordered sets and maps
[pulseview.git] / pv / views / trace / view.cpp
index c8a7a2b88d08ce8aff081dee9c98c39238f27e53..93bcb52072f01ea2e7fe61b433e5849606013323 100644 (file)
 #include <cmath>
 #include <iostream>
 #include <iterator>
-#include <unordered_set>
-
-#include <boost/archive/text_iarchive.hpp>
-#include <boost/archive/text_oarchive.hpp>
-#include <boost/serialization/serialization.hpp>
 
 #include <QApplication>
+#include <QDebug>
 #include <QEvent>
 #include <QFontMetrics>
 #include <QMenu>
@@ -55,6 +51,7 @@
 #include "view.hpp"
 #include "viewport.hpp"
 
+#include "pv/metadata_obj.hpp"
 #include "pv/data/logic.hpp"
 #include "pv/data/logicsegment.hpp"
 #include "pv/devices/device.hpp"
@@ -84,9 +81,6 @@ using std::pair;
 using std::set;
 using std::set_difference;
 using std::shared_ptr;
-using std::stringstream;
-using std::unordered_map;
-using std::unordered_set;
 using std::vector;
 
 namespace pv {
@@ -98,8 +92,10 @@ const Timestamp View::MinScale("1e-12");
 
 const int View::MaxScrollValue = INT_MAX / 2;
 
-const int View::ScaleUnits[3] = {1, 2, 5};
+/* Area at the top and bottom of the view that can't be scrolled out of sight */
+const int View::ViewScrollMargin = 50;
 
+const int View::ScaleUnits[3] = {1, 2, 5};
 
 CustomScrollArea::CustomScrollArea(QWidget *parent) :
        QAbstractScrollArea(parent)
@@ -184,6 +180,13 @@ View::View(Session &session, bool is_main_view, QMainWindow *parent) :
 
        GlobalSettings::add_change_handler(this);
 
+       // Set up metadata objects and event handlers
+       if (is_main_view) {
+               session_.metadata_obj_manager()->create_object(MetadataObjMainViewRange);
+               session_.metadata_obj_manager()->create_object(MetadataObjMousePos);
+       }
+
+       // Set up UI event handlers
        connect(scrollarea_->horizontalScrollBar(), SIGNAL(valueChanged(int)),
                this, SLOT(h_scroll_value_changed(int)));
        connect(scrollarea_->verticalScrollBar(), SIGNAL(valueChanged(int)),
@@ -205,10 +208,11 @@ View::View(Session &session, bool is_main_view, QMainWindow *parent) :
        connect(&lazy_event_handler_, SIGNAL(timeout()),
                this, SLOT(process_sticky_events()));
        lazy_event_handler_.setSingleShot(true);
+       lazy_event_handler_.setInterval(1000 / ViewBase::MaxViewAutoUpdateRate);
 
        // Set up local keyboard shortcuts
        zoom_in_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Plus), this,
-                       SLOT(on_zoom_in_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
+               SLOT(on_zoom_in_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
        zoom_in_shortcut_->setAutoRepeat(false);
 
        zoom_out_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Minus), this,
@@ -228,17 +232,20 @@ View::View(Session &session, bool is_main_view, QMainWindow *parent) :
                SLOT(on_scroll_to_end_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
        end_shortcut_->setAutoRepeat(false);
 
-       grab_ruler_left_shortcut_ = new QShortcut(QKeySequence(Qt::Key_1), this);
+       grab_ruler_left_shortcut_ = new QShortcut(QKeySequence(Qt::Key_1), this,
+               nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
        connect(grab_ruler_left_shortcut_, &QShortcut::activated,
                this, [=]{on_grab_ruler(1);});
        grab_ruler_left_shortcut_->setAutoRepeat(false);
 
-       grab_ruler_right_shortcut_ = new QShortcut(QKeySequence(Qt::Key_2), this);
+       grab_ruler_right_shortcut_ = new QShortcut(QKeySequence(Qt::Key_2), this,
+               nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
        connect(grab_ruler_right_shortcut_, &QShortcut::activated,
                this, [=]{on_grab_ruler(2);});
        grab_ruler_right_shortcut_->setAutoRepeat(false);
 
-       cancel_grab_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this);
+       cancel_grab_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this,
+               nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
        connect(cancel_grab_shortcut_, &QShortcut::activated,
                this, [=]{grabbed_widget_ = nullptr;});
        cancel_grab_shortcut_->setAutoRepeat(false);
@@ -273,6 +280,8 @@ void View::reset_view_state()
        scale_ = 1e-3;
        offset_ = 0;
        ruler_offset_ = 0;
+       zero_offset_ = 0;
+       custom_zero_offset_set_ = false;
        updating_scroll_ = false;
        settings_restored_ = false;
        always_zoom_to_fit_ = false;
@@ -314,7 +323,7 @@ const Session& View::session() const
        return session_;
 }
 
-unordered_set< shared_ptr<Signal> > View::signals() const
+vector< shared_ptr<Signal> > View::signals() const
 {
        return signals_;
 }
@@ -341,7 +350,7 @@ void View::clear_signals()
 void View::add_signal(const shared_ptr<Signal> signal)
 {
        ViewBase::add_signalbase(signal->base());
-       signals_.insert(signal);
+       signals_.push_back(signal);
 
        signal->set_segment_display_mode(segment_display_mode_);
        signal->set_current_segment(current_segment_);
@@ -429,12 +438,12 @@ void View::save_settings(QSettings &settings) const
        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()));
-       }
+       GlobalSettings::store_timestamp(settings, "offset", offset_);
+
+       if (custom_zero_offset_set_)
+               GlobalSettings::store_timestamp(settings, "zero_offset", -zero_offset_);
+       else
+               settings.remove("zero_offset");
 
        for (const shared_ptr<Signal>& signal : signals_) {
                settings.beginGroup(signal->base()->internal_name());
@@ -452,20 +461,13 @@ void View::restore_settings(QSettings &settings)
                set_scale(settings.value("scale").toDouble());
 
        if (settings.contains("offset")) {
-               util::Timestamp offset;
-               stringstream ss;
-               ss << settings.value("offset").toString().toStdString();
-
-               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";
-               }
+               // This also updates ruler_offset_
+               set_offset(GlobalSettings::restore_timestamp(settings, "offset"));
        }
 
+       if (settings.contains("zero_offset"))
+               set_zero_position(GlobalSettings::restore_timestamp(settings, "zero_offset"));
+
        if (settings.contains("splitter_state"))
                splitter_->restoreState(settings.value("splitter_state").toByteArray());
 
@@ -528,6 +530,22 @@ void View::set_offset(const pv::util::Timestamp& offset, bool force_update)
        if ((offset_ != offset) || force_update) {
                offset_ = offset;
                ruler_offset_ = offset_ + zero_offset_;
+
+               const int w = viewport_->width();
+               if (w > 0) {
+                       const double samplerate = session_.get_samplerate();
+                       // Note: sample_num = time * samplerate
+                       // Note: samples_per_pixel = samplerate * scale
+                       int64_t start_sample = (offset_ * samplerate).convert_to<int64_t>();
+                       int64_t end_sample = (offset_ * samplerate).convert_to<int64_t>() +
+                               (w * session_.get_samplerate() * scale_);
+
+                       MetadataObject* md_obj =
+                               session_.metadata_obj_manager()->find_object_by_type(MetadataObjMainViewRange);
+                       md_obj->set_value(MetadataValueStartSample, QVariant((qlonglong)start_sample));
+                       md_obj->set_value(MetadataValueEndSample, QVariant((qlonglong)end_sample));
+               }
+
                offset_changed();
        }
 }
@@ -545,6 +563,7 @@ const Timestamp& View::ruler_offset() const
 void View::set_zero_position(const pv::util::Timestamp& position)
 {
        zero_offset_ = -position;
+       custom_zero_offset_set_ = true;
 
        // Force an immediate update of the offsets
        set_offset(offset_, true);
@@ -555,6 +574,19 @@ void View::reset_zero_position()
 {
        zero_offset_ = 0;
 
+       // 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) {
+               vector<util::Timestamp> triggers = session_.get_triggers(current_segment_);
+
+               if (triggers.size() > 0)
+                       zero_offset_ = triggers.front();
+       }
+
+       custom_zero_offset_set_ = false;
+
        // Force an immediate update of the offsets
        set_offset(offset_, true);
        ruler_->update();
@@ -673,12 +705,8 @@ void View::set_current_segment(uint32_t segment_id)
        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());
+       if (!custom_zero_offset_set_)
+               reset_zero_position();
 
        viewport_->update();
 
@@ -776,6 +804,26 @@ void View::zoom_fit(bool gui_state)
        set_scale_offset(scale.convert_to<double>(), extents.first);
 }
 
+void View::focus_on_range(uint64_t start_sample, uint64_t end_sample)
+{
+       assert(viewport_);
+       const int w = viewport_->width();
+       if (w <= 0)
+               return;
+
+       const double samplerate = session_.get_samplerate();
+
+       const uint64_t sample_delta = (end_sample - start_sample);
+
+       // Note: We add 20% margin on the left and 5% on the right
+       const Timestamp delta = (sample_delta * 1.25) / samplerate;
+
+       const Timestamp scale = max(min(delta / w, MaxScale), MinScale);
+       const Timestamp offset = (start_sample - sample_delta * 0.20) / samplerate;
+
+       set_scale_offset(scale.convert_to<double>(), offset);
+}
+
 void View::set_scale_offset(double scale, const Timestamp& offset)
 {
        // Disable sticky scrolling / always zoom to fit when acquisition runs
@@ -804,13 +852,13 @@ void View::set_scale_offset(double scale, const Timestamp& offset)
        viewport_->update();
 }
 
-set< shared_ptr<SignalData> > View::get_visible_data() const
+vector< shared_ptr<SignalData> > View::get_visible_data() const
 {
        // Make a set of all the visible data objects
-       set< shared_ptr<SignalData> > visible_data;
+       vector< shared_ptr<SignalData> > visible_data;
        for (const shared_ptr<Signal>& sig : signals_)
                if (sig->enabled())
-                       visible_data.insert(sig->data());
+                       visible_data.push_back(sig->data());
 
        return visible_data;
 }
@@ -818,8 +866,14 @@ set< shared_ptr<SignalData> > View::get_visible_data() const
 pair<Timestamp, Timestamp> View::get_time_extents() const
 {
        boost::optional<Timestamp> left_time, right_time;
-       const set< shared_ptr<SignalData> > visible_data = get_visible_data();
-       for (const shared_ptr<SignalData>& d : visible_data) {
+
+       vector< shared_ptr<SignalData> > data;
+       if (signals_.size() == 0)
+               return make_pair(0, 0);
+
+       data.push_back(signals_.front()->data());
+
+       for (const shared_ptr<SignalData>& d : data) {
                const vector< shared_ptr<Segment> > segments = d->segments();
                for (const shared_ptr<Segment>& s : segments) {
                        double samplerate = s->samplerate();
@@ -874,7 +928,7 @@ void View::set_cursors(pv::util::Timestamp& first, pv::util::Timestamp& second)
        viewport_->update();
 }
 
-void View::centre_cursors()
+void View::center_cursors()
 {
        assert(cursors_);
 
@@ -1085,15 +1139,8 @@ void View::trigger_event(int segment_id, util::Timestamp location)
        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);
+       if (!custom_zero_offset_set_)
+               reset_zero_position();
 
        trigger_markers_.push_back(make_shared<TriggerMarker>(*this, location));
 }
@@ -1248,9 +1295,13 @@ void View::update_scroll()
        const pair<int, int> extents = v_extents();
 
        // Don't change the scrollbar range if there are no traces
-       if (extents.first != extents.second)
-               vscrollbar->setRange(extents.first - areaSize.height(),
-                       extents.second);
+       if (extents.first != extents.second) {
+               int top_margin = ViewScrollMargin;
+               int btm_margin = ViewScrollMargin;
+
+               vscrollbar->setRange(extents.first - areaSize.height() + top_margin,
+                       extents.second - btm_margin);
+       }
 
        if (scroll_needs_defaults_) {
                set_scroll_default();
@@ -1321,12 +1372,11 @@ void View::update_layout()
 
 TraceTreeItemOwner* View::find_prevalent_trace_group(
        const shared_ptr<sigrok::ChannelGroup> &group,
-       const unordered_map<shared_ptr<data::SignalBase>, shared_ptr<Signal> >
-               &signal_map)
+       const map<shared_ptr<data::SignalBase>, shared_ptr<Signal> > &signal_map)
 {
        assert(group);
 
-       unordered_set<TraceTreeItemOwner*> owners;
+       set<TraceTreeItemOwner*> owners;
        vector<TraceTreeItemOwner*> owner_list;
 
        // Make a set and a list of all the owners
@@ -1358,8 +1408,7 @@ TraceTreeItemOwner* View::find_prevalent_trace_group(
 
 vector< shared_ptr<Trace> > View::extract_new_traces_for_channels(
        const vector< shared_ptr<sigrok::Channel> > &channels,
-       const unordered_map<shared_ptr<data::SignalBase>, shared_ptr<Signal> >
-               &signal_map,
+       const map<shared_ptr<data::SignalBase>, shared_ptr<Signal> > &signal_map,
        set< shared_ptr<Trace> > &add_list)
 {
        vector< shared_ptr<Trace> > filtered_traces;
@@ -1519,8 +1568,19 @@ void View::update_hover_point()
        for (const shared_ptr<TraceTreeItem>& r : trace_tree_items)
                r->hover_point_changed(hover_point_);
 
-       // Notify any other listeners
+       // Notify this view's listeners
        hover_point_changed(hover_widget_, hover_point_);
+
+       // Hover point is -1 when invalid and 0 for the header
+       if (hover_point_.x() > 0) {
+               // Notify global listeners
+               pv::util::Timestamp mouse_time = offset_ + hover_point_.x() * scale_;
+               int64_t sample_num = (mouse_time * session_.get_samplerate()).convert_to<int64_t>();
+
+               MetadataObject* md_obj =
+                       session_.metadata_obj_manager()->find_object_by_type(MetadataObjMousePos);
+               md_obj->set_value(MetadataValueStartSample, QVariant((qlonglong)sample_num));
+       }
 }
 
 void View::row_item_appearance_changed(bool label, bool content)
@@ -1550,8 +1610,8 @@ void View::extents_changed(bool horz, bool vert)
                (horz ? TraceTreeItemHExtentsChanged : 0) |
                (vert ? TraceTreeItemVExtentsChanged : 0);
 
-       lazy_event_handler_.stop();
-       lazy_event_handler_.start();
+       if (!lazy_event_handler_.isActive())
+               lazy_event_handler_.start();
 }
 
 void View::on_signal_name_changed()
@@ -1623,18 +1683,22 @@ void View::v_scroll_value_changed()
 
 void View::on_grab_ruler(int ruler_id)
 {
-       if (cursors_shown()) {
-               // Release the grabbed widget if its trigger hotkey was pressed twice
-               if (ruler_id == 1)
-                       grabbed_widget_ = (grabbed_widget_ == cursors_->first().get()) ?
-                               nullptr : cursors_->first().get();
-               else
-                       grabbed_widget_ = (grabbed_widget_ == cursors_->second().get()) ?
-                               nullptr : cursors_->second().get();
-
-               if (grabbed_widget_)
-                       grabbed_widget_->set_time(offset_ + mapFromGlobal(QCursor::pos()).x() * scale_);
+       if (!cursors_shown()) {
+               center_cursors();
+               show_cursors();
        }
+
+       // Release the grabbed widget if its trigger hotkey was pressed twice
+       if (ruler_id == 1)
+               grabbed_widget_ = (grabbed_widget_ == cursors_->first().get()) ?
+                       nullptr : cursors_->first().get();
+       else
+               grabbed_widget_ = (grabbed_widget_ == cursors_->second().get()) ?
+                       nullptr : cursors_->second().get();
+
+       if (grabbed_widget_)
+               grabbed_widget_->set_time(ruler_->get_absolute_time_from_x_pos(
+                       mapFromGlobal(QCursor::pos()).x() - header_width()));
 }
 
 void View::signals_changed()
@@ -1685,8 +1749,7 @@ void View::signals_changed()
                inserter(remove_traces, remove_traces.begin()));
 
        // Make a look-up table of sigrok Channels to pulseview Signals
-       unordered_map<shared_ptr<data::SignalBase>, shared_ptr<Signal> >
-               signal_map;
+       map<shared_ptr<data::SignalBase>, shared_ptr<Signal> > signal_map;
        for (const shared_ptr<Signal>& sig : signals_)
                signal_map[sig->base()] = sig;
 
@@ -1787,7 +1850,7 @@ void View::signals_changed()
 
        // Add and position the pending top levels items
        int offset = v_extents().second;
-       for (auto item : new_top_level_items) {
+       for (shared_ptr<TraceTreeItem> item : new_top_level_items) {
                add_child_item(item);
 
                // Position the item after the last item or at the top if there is none
@@ -1824,7 +1887,8 @@ void View::capture_state_updated(int state)
                set_time_unit(util::TimeUnit::Samples);
 
                trigger_markers_.clear();
-               set_zero_position(0);
+               if (!custom_zero_offset_set_)
+                       set_zero_position(0);
 
                scale_at_acq_start_ = scale_;
                offset_at_acq_start_ = offset_;
@@ -1907,12 +1971,9 @@ void View::on_segment_changed(int segment)
 
 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
+       (void)new_value;
+
+       if (!custom_zero_offset_set_)
                reset_zero_position();
 }