Fix #684 by implementing snap-to-edge for TimeItem-based classes
authorSoeren Apel <soeren@apelpie.net>
Wed, 26 Sep 2018 20:50:24 +0000 (22:50 +0200)
committerSoeren Apel <soeren@apelpie.net>
Wed, 26 Sep 2018 21:00:58 +0000 (23:00 +0200)
16 files changed:
pv/data/analog.cpp
pv/data/analog.hpp
pv/data/logic.cpp
pv/data/logic.hpp
pv/data/signalbase.cpp
pv/data/signalbase.hpp
pv/data/signaldata.hpp
pv/views/trace/analogsignal.cpp
pv/views/trace/analogsignal.hpp
pv/views/trace/logicsignal.cpp
pv/views/trace/logicsignal.hpp
pv/views/trace/signal.hpp
pv/views/trace/timeitem.cpp
pv/views/trace/timemarker.cpp
pv/views/trace/view.cpp
pv/views/trace/view.hpp

index 364ee5a818b57b6f82eed03473c1f5e368dd3af8..da025882949ea8486638198fcb5fe124c22fa7ff 100644 (file)
@@ -63,6 +63,14 @@ void Analog::clear()
        samples_cleared();
 }
 
+double Analog::get_samplerate() const
+{
+       if (segments_.empty())
+               return 1.0;
+
+       return segments_.front()->samplerate();
+}
+
 uint64_t Analog::max_sample_count() const
 {
        uint64_t l = 0;
index 859b3e013f473fc011a5a1eb5b6a48e5f2ae6d32..c0b9a0134d72d9b5028f75b5d19040a71d81c991 100644 (file)
@@ -53,6 +53,8 @@ public:
 
        void clear();
 
+       double get_samplerate() const;
+
        uint64_t max_sample_count() const;
 
        void notify_samples_added(QObject* segment, uint64_t start_sample,
index e78c28468d71ef42da07e7e0eb102076889cfd38..b094a0c828334059029936cd48d40cbd22844dc8 100644 (file)
@@ -69,6 +69,14 @@ void Logic::clear()
        samples_cleared();
 }
 
+double Logic::get_samplerate() const
+{
+       if (segments_.empty())
+               return 1.0;
+
+       return segments_.front()->samplerate();
+}
+
 uint64_t Logic::max_sample_count() const
 {
        uint64_t l = 0;
index d11e0cca67677b5a833ef66fb9390ed4237a406e..e0fb7dd14dd05dab2dc93451d999350f0d6dd60f 100644 (file)
@@ -54,6 +54,8 @@ public:
 
        void clear();
 
+       double get_samplerate() const;
+
        uint64_t max_sample_count() const;
 
        void notify_samples_added(QObject* segment, uint64_t start_sample,
index 791094036ddcb23511329a9922c67f1774fa0268..01206e7e24b1eae570a3626b25468f1c8546389d 100644 (file)
@@ -265,6 +265,26 @@ bool SignalBase::has_samples() const
        return result;
 }
 
+double SignalBase::get_samplerate() const
+{
+       if (channel_type_ == AnalogChannel)
+       {
+               shared_ptr<Analog> data = dynamic_pointer_cast<Analog>(data_);
+               if (data)
+                       return data->get_samplerate();
+       }
+
+       if (channel_type_ == LogicChannel)
+       {
+               shared_ptr<Logic> data = dynamic_pointer_cast<Logic>(data_);
+               if (data)
+                       return data->get_samplerate();
+       }
+
+       // Default samplerate is 1 Hz
+       return 1.0;
+}
+
 SignalBase::ConversionType SignalBase::get_conversion_type() const
 {
        return conversion_type_;
index b6f69ef56e0950d628c0b55234f0bbf6770a42a5..c3e0d3d64ef56d02b289451c7cb3d54ca89c44f9 100644 (file)
@@ -192,6 +192,11 @@ public:
         */
        bool has_samples() const;
 
+       /**
+        * Returns the sample rate for this signal.
+        */
+       double get_samplerate() const;
+
        /**
         * Queries the kind of conversion performed on this channel.
         */
index 01dd93ab32a0819fe8a8acf2d709cd0a6945131c..5168fee8e4b58fb6c75dec7afe26364a4709d47a 100644 (file)
@@ -50,6 +50,8 @@ public:
        virtual void clear() = 0;
 
        virtual uint64_t max_sample_count() const = 0;
+
+       virtual double get_samplerate() const = 0;
 };
 
 } // namespace data
index ac5e83c834880f4e85f5f9fd2c8203ebf017301b..0419b03b698eb208496e15b75957c2eb8b6d9663 100644 (file)
@@ -62,6 +62,7 @@ using std::pair;
 using std::shared_ptr;
 using std::vector;
 
+using pv::data::LogicSegment;
 using pv::data::SignalBase;
 using pv::util::SIPrefix;
 
@@ -763,6 +764,37 @@ void AnalogSignal::update_conversion_widgets()
        conv_threshold_cb_->blockSignals(false);
 }
 
+vector<data::LogicSegment::EdgePair> AnalogSignal::get_nearest_level_changes(uint64_t sample_pos)
+{
+       assert(base_);
+       assert(owner_);
+
+       // Return if there's no logic data or we're showing only the analog trace
+       if (!base_->logic_data() || (display_type_ == DisplayAnalog))
+               return vector<data::LogicSegment::EdgePair>();
+
+       if (sample_pos == 0)
+               return vector<LogicSegment::EdgePair>();
+
+       shared_ptr<LogicSegment> segment = get_logic_segment_to_paint();
+       if (!segment || (segment->get_sample_count() == 0))
+               return vector<LogicSegment::EdgePair>();
+
+       const View *view = owner_->view();
+       assert(view);
+       const double samples_per_pixel = base_->get_samplerate() * view->scale();
+
+       vector<LogicSegment::EdgePair> edges;
+
+       segment->get_surrounding_edges(edges, sample_pos,
+               samples_per_pixel / LogicSignal::Oversampling, 0);
+
+       if (edges.empty())
+               return vector<LogicSegment::EdgePair>();
+
+       return edges;
+}
+
 void AnalogSignal::perform_autoranging(bool keep_divs, bool force_update)
 {
        const deque< shared_ptr<pv::data::AnalogSegment> > &segments =
index 885856dff999a84d5428cff55eeb43de8a102d97..60acc2eb68ccebe0eba775bc3c141122f02eac20 100644 (file)
@@ -141,6 +141,15 @@ private:
 
        void update_conversion_widgets();
 
+       /**
+        * Determines the closest level change (i.e. edge) to a given sample, which
+        * is useful for e.g. the "snap to edge" functionality.
+        *
+        * @param sample_pos Sample to use
+        * @return The changes left and right of the given position
+        */
+       virtual vector<data::LogicSegment::EdgePair> get_nearest_level_changes(uint64_t sample_pos);
+
        void perform_autoranging(bool keep_divs, bool force_update);
 
        void reset_pixel_values();
index 438e4e21c1f6a6c639543897cb65f35362e9a746..6a9bf432a07157c2a2e7713e60dbd7972c8abd8f 100644 (file)
@@ -57,6 +57,8 @@ using sigrok::Trigger;
 using sigrok::TriggerMatch;
 using sigrok::TriggerMatchType;
 
+using pv::data::LogicSegment;
+
 namespace pv {
 namespace views {
 namespace trace {
@@ -181,7 +183,7 @@ void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
        const float high_offset = y - signal_height_ + 0.5f;
        const float low_offset = y + 0.5f;
 
-       shared_ptr<pv::data::LogicSegment> segment = get_logic_segment_to_paint();
+       shared_ptr<LogicSegment> segment = get_logic_segment_to_paint();
        if (!segment || (segment->get_sample_count() == 0))
                return;
 
@@ -320,49 +322,31 @@ void LogicSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp)
        }
 }
 
-void LogicSignal::hover_point_changed(const QPoint &hp)
+vector<LogicSegment::EdgePair> LogicSignal::get_nearest_level_changes(uint64_t sample_pos)
 {
-       Signal::hover_point_changed(hp);
-
        assert(base_);
        assert(owner_);
 
-       if ((!base_->enabled()) || (hp.x() == 0))
-               return;
-
-       // Ignore if mouse cursor is not hovering over this trace
-       const int y = get_visual_y();
-       const pair<int, int> extents = v_extents();
-       if ((hp.y() < (y + extents.first)) || ((hp.y() > (y + extents.second))))
-               return;
+       if (sample_pos == 0)
+               return vector<LogicSegment::EdgePair>();
 
-       shared_ptr<pv::data::LogicSegment> segment = get_logic_segment_to_paint();
+       shared_ptr<LogicSegment> segment = get_logic_segment_to_paint();
        if (!segment || (segment->get_sample_count() == 0))
-               return;
-
-       double samplerate = segment->samplerate();
-
-       // Show sample rate as 1Hz when it is unknown
-       if (samplerate == 0.0)
-               samplerate = 1.0;
+               return vector<LogicSegment::EdgePair>();
 
        const View *view = owner_->view();
        assert(view);
-       const double scale = view->scale();
-       const double pixels_offset =
-               ((view->offset() - segment->start_time()) / scale).convert_to<double>();
-       const double samples_per_pixel = samplerate * scale;
-
-       const uint64_t sample_pos = (uint64_t)max(
-               (hp.x() + pixels_offset) * samples_per_pixel, 0.0);
+       const double samples_per_pixel = base_->get_samplerate() * view->scale();
 
-       vector<data::LogicSegment::EdgePair> edges;
+       vector<LogicSegment::EdgePair> edges;
 
        segment->get_surrounding_edges(edges, sample_pos,
                samples_per_pixel / Oversampling, base_->index());
 
        if (edges.empty())
-               return;
+               return vector<LogicSegment::EdgePair>();
+
+       return edges;
 }
 
 void LogicSignal::paint_caps(QPainter &p, QLineF *const lines,
index ca5ce3d4d2f38ba922971c4c67dc5f6248e6ab4f..10ede729d1b53b630613d56740ec6243339b9649 100644 (file)
@@ -103,7 +103,14 @@ public:
         */
        virtual void paint_fore(QPainter &p, ViewItemPaintParams &pp);
 
-       virtual void hover_point_changed(const QPoint &hp);
+       /**
+        * Determines the closest level change (i.e. edge) to a given sample, which
+        * is useful for e.g. the "snap to edge" functionality.
+        *
+        * @param sample_pos Sample to use
+        * @return The changes left and right of the given position
+        */
+       virtual vector<data::LogicSegment::EdgePair> get_nearest_level_changes(uint64_t sample_pos);
 
 private:
        void paint_caps(QPainter &p, QLineF *const lines,
index 1c53d8b2260a960669261c056f069a9e35883bfb..1b9f254330a79fd150f6f73165f5cb910a76b5be 100644 (file)
@@ -27,6 +27,8 @@
 
 #include <cstdint>
 
+#include <pv/data/logicsegment.hpp>
+
 #include "trace.hpp"
 #include "viewitemowner.hpp"
 
@@ -68,6 +70,15 @@ public:
 
        virtual shared_ptr<pv::data::SignalData> data() const = 0;
 
+       /**
+        * Determines the closest level change (i.e. edge) to a given sample, which
+        * is useful for e.g. the "snap to edge" functionality.
+        *
+        * @param sample_pos Sample to use
+        * @return The changes left and right of the given position
+        */
+       virtual vector<data::LogicSegment::EdgePair> get_nearest_level_changes(uint64_t sample_pos) = 0;
+
        /**
         * Returns true if the trace is visible and enabled.
         */
index bd07e9b28c9383f1c4a48fbe3cd3bdd6b40ae6b4..47a7da3c503bbd616d5085a66b4ea5c8c103c74d 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
+#include "signal.hpp"
 #include "timeitem.hpp"
 #include "view.hpp"
 
@@ -30,8 +31,13 @@ TimeItem::TimeItem(View &view) :
 
 void TimeItem::drag_by(const QPoint &delta)
 {
-       set_time(view_.offset() + (drag_point_.x() + delta.x() - 0.5) *
-               view_.scale());
+       int64_t sample_num = view_.get_nearest_level_change(drag_point_ + delta);
+
+       if (sample_num > -1)
+               set_time(sample_num / view_.get_signal_under_mouse_cursor()->base()->get_samplerate());
+       else
+               set_time(view_.offset() + (drag_point_.x() + delta.x() - 0.5) *
+                       view_.scale());
 }
 
 } // namespace trace
index 65b9481d12ff8261d321143d01b2add95c38ae5a..266007a57053c220f59a87fe80eb630438a413c0 100644 (file)
@@ -80,7 +80,9 @@ float TimeMarker::get_x() const
 
 QPoint TimeMarker::drag_point(const QRect &rect) const
 {
-       return QPoint(get_x(), rect.bottom());
+       (void)rect;
+
+       return QPoint(get_x(), view_.mapFromGlobal(QCursor::pos()).y());
 }
 
 QRectF TimeMarker::label_rect(const QRectF &rect) const
index 65b06aea82cba76279f185f40b4af933578a18dd..b9872130890f4e124c9cb052680f852e68124757 100644 (file)
@@ -320,6 +320,11 @@ void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
 }
 #endif
 
+shared_ptr<Signal> View::get_signal_under_mouse_cursor() const
+{
+       return signal_under_mouse_cursor_;
+}
+
 View* View::view()
 {
        return this;
@@ -853,6 +858,43 @@ const QPoint& View::hover_point() const
        return hover_point_;
 }
 
+int64_t View::get_nearest_level_change(const QPoint &p) const
+{
+       shared_ptr<Signal> signal = signal_under_mouse_cursor_;
+
+       if (!signal)
+               return -1;
+
+       // Calculate sample number from cursor position
+       const double samples_per_pixel = signal->base()->get_samplerate() * scale();
+       const int64_t x_offset = offset().convert_to<double>() / scale();
+       const int64_t sample_num = max(((x_offset + p.x()) * samples_per_pixel), 0.0);
+
+       // Query for nearest level changes
+       vector<data::LogicSegment::EdgePair> edges =
+               signal->get_nearest_level_changes(sample_num);
+
+       if (edges.size() != 2)
+               return -1;
+
+       // We received absolute sample numbers, make them relative
+       const int64_t left_sample_delta = sample_num - edges.front().first;
+       const int64_t right_sample_delta = edges.back().first - sample_num - 1;
+
+       const int64_t left_delta = left_sample_delta / samples_per_pixel;
+       const int64_t right_delta = right_sample_delta / samples_per_pixel;
+
+       int64_t nearest = -1;
+
+       // Only use closest left or right edge if they're close to the cursor
+       if ((left_delta < right_delta) && (left_delta < 15))
+               nearest = edges.front().first;
+       if ((left_delta >= right_delta) && (right_delta < 15))
+               nearest = edges.back().first;
+
+       return nearest;
+}
+
 void View::restack_all_trace_tree_items()
 {
        // Make a list of owners that is sorted from deepest first
@@ -1280,11 +1322,24 @@ void View::resizeEvent(QResizeEvent* event)
 
 void View::update_hover_point()
 {
+       // Determine signal that the mouse cursor is hovering over
+       signal_under_mouse_cursor_.reset();
+       for (shared_ptr<Signal> s : signals_) {
+               const pair<int, int> extents = s->v_extents();
+               const int top = s->get_visual_y() + extents.first;
+               const int btm = s->get_visual_y() + extents.second;
+               if ((hover_point_.y() >= top) && (hover_point_.y() <= btm)
+                       && s->base()->enabled())
+                       signal_under_mouse_cursor_ = s;
+       }
+
+       // Update all trace tree items
        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_);
 
+       // Notify any other listeners
        hover_point_changed(hover_point_);
 }
 
index 8e78b6260e2143dcbb141ad2a5aa0f1b4836c822..94082189be41bc1b47b7abfff094045ed703138c 100644 (file)
@@ -129,6 +129,8 @@ public:
        virtual void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
 #endif
 
+       shared_ptr<Signal> get_signal_under_mouse_cursor() const;
+
        /**
         * Returns the view of the owner.
         */
@@ -301,6 +303,15 @@ public:
 
        const QPoint& hover_point() const;
 
+       /**
+        * Determines the closest level change (i.e. edge) to a given point, which
+        * is useful for e.g. the "snap to edge" functionality.
+        *
+        * @param p The current position of the mouse cursor
+        * @return The sample number of the nearest level change or -1 if none
+        */
+       int64_t get_nearest_level_change(const QPoint &p) const;
+
        void restack_all_trace_tree_items();
 
        int header_width() const;
@@ -517,6 +528,7 @@ private:
        vector< shared_ptr<TriggerMarker> > trigger_markers_;
 
        QPoint hover_point_;
+       shared_ptr<Signal> signal_under_mouse_cursor_;
 
        unsigned int sticky_events_;
        QTimer lazy_event_handler_;