From eeceee9955e7db4ac777d49d1b7a766069476b08 Mon Sep 17 00:00:00 2001 From: Soeren Apel Date: Wed, 26 Sep 2018 22:50:24 +0200 Subject: [PATCH] Fix #684 by implementing snap-to-edge for TimeItem-based classes --- pv/data/analog.cpp | 8 +++++ pv/data/analog.hpp | 2 ++ pv/data/logic.cpp | 8 +++++ pv/data/logic.hpp | 2 ++ pv/data/signalbase.cpp | 20 ++++++++++++ pv/data/signalbase.hpp | 5 +++ pv/data/signaldata.hpp | 2 ++ pv/views/trace/analogsignal.cpp | 32 +++++++++++++++++++ pv/views/trace/analogsignal.hpp | 9 ++++++ pv/views/trace/logicsignal.cpp | 42 ++++++++----------------- pv/views/trace/logicsignal.hpp | 9 +++++- pv/views/trace/signal.hpp | 11 +++++++ pv/views/trace/timeitem.cpp | 10 ++++-- pv/views/trace/timemarker.cpp | 4 ++- pv/views/trace/view.cpp | 55 +++++++++++++++++++++++++++++++++ pv/views/trace/view.hpp | 12 +++++++ 16 files changed, 198 insertions(+), 33 deletions(-) diff --git a/pv/data/analog.cpp b/pv/data/analog.cpp index 364ee5a..da02588 100644 --- a/pv/data/analog.cpp +++ b/pv/data/analog.cpp @@ -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; diff --git a/pv/data/analog.hpp b/pv/data/analog.hpp index 859b3e0..c0b9a01 100644 --- a/pv/data/analog.hpp +++ b/pv/data/analog.hpp @@ -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, diff --git a/pv/data/logic.cpp b/pv/data/logic.cpp index e78c284..b094a0c 100644 --- a/pv/data/logic.cpp +++ b/pv/data/logic.cpp @@ -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; diff --git a/pv/data/logic.hpp b/pv/data/logic.hpp index d11e0cc..e0fb7dd 100644 --- a/pv/data/logic.hpp +++ b/pv/data/logic.hpp @@ -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, diff --git a/pv/data/signalbase.cpp b/pv/data/signalbase.cpp index 7910940..01206e7 100644 --- a/pv/data/signalbase.cpp +++ b/pv/data/signalbase.cpp @@ -265,6 +265,26 @@ bool SignalBase::has_samples() const return result; } +double SignalBase::get_samplerate() const +{ + if (channel_type_ == AnalogChannel) + { + shared_ptr data = dynamic_pointer_cast(data_); + if (data) + return data->get_samplerate(); + } + + if (channel_type_ == LogicChannel) + { + shared_ptr data = dynamic_pointer_cast(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_; diff --git a/pv/data/signalbase.hpp b/pv/data/signalbase.hpp index b6f69ef..c3e0d3d 100644 --- a/pv/data/signalbase.hpp +++ b/pv/data/signalbase.hpp @@ -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. */ diff --git a/pv/data/signaldata.hpp b/pv/data/signaldata.hpp index 01dd93a..5168fee 100644 --- a/pv/data/signaldata.hpp +++ b/pv/data/signaldata.hpp @@ -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 diff --git a/pv/views/trace/analogsignal.cpp b/pv/views/trace/analogsignal.cpp index ac5e83c..0419b03 100644 --- a/pv/views/trace/analogsignal.cpp +++ b/pv/views/trace/analogsignal.cpp @@ -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 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(); + + if (sample_pos == 0) + return vector(); + + shared_ptr segment = get_logic_segment_to_paint(); + if (!segment || (segment->get_sample_count() == 0)) + return vector(); + + const View *view = owner_->view(); + assert(view); + const double samples_per_pixel = base_->get_samplerate() * view->scale(); + + vector edges; + + segment->get_surrounding_edges(edges, sample_pos, + samples_per_pixel / LogicSignal::Oversampling, 0); + + if (edges.empty()) + return vector(); + + return edges; +} + void AnalogSignal::perform_autoranging(bool keep_divs, bool force_update) { const deque< shared_ptr > &segments = diff --git a/pv/views/trace/analogsignal.hpp b/pv/views/trace/analogsignal.hpp index 885856d..60acc2e 100644 --- a/pv/views/trace/analogsignal.hpp +++ b/pv/views/trace/analogsignal.hpp @@ -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 get_nearest_level_changes(uint64_t sample_pos); + void perform_autoranging(bool keep_divs, bool force_update); void reset_pixel_values(); diff --git a/pv/views/trace/logicsignal.cpp b/pv/views/trace/logicsignal.cpp index 438e4e2..6a9bf43 100644 --- a/pv/views/trace/logicsignal.cpp +++ b/pv/views/trace/logicsignal.cpp @@ -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 segment = get_logic_segment_to_paint(); + shared_ptr 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 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 extents = v_extents(); - if ((hp.y() < (y + extents.first)) || ((hp.y() > (y + extents.second)))) - return; + if (sample_pos == 0) + return vector(); - shared_ptr segment = get_logic_segment_to_paint(); + shared_ptr 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(); const View *view = owner_->view(); assert(view); - const double scale = view->scale(); - const double pixels_offset = - ((view->offset() - segment->start_time()) / scale).convert_to(); - 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 edges; + vector edges; segment->get_surrounding_edges(edges, sample_pos, samples_per_pixel / Oversampling, base_->index()); if (edges.empty()) - return; + return vector(); + + return edges; } void LogicSignal::paint_caps(QPainter &p, QLineF *const lines, diff --git a/pv/views/trace/logicsignal.hpp b/pv/views/trace/logicsignal.hpp index ca5ce3d..10ede72 100644 --- a/pv/views/trace/logicsignal.hpp +++ b/pv/views/trace/logicsignal.hpp @@ -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 get_nearest_level_changes(uint64_t sample_pos); private: void paint_caps(QPainter &p, QLineF *const lines, diff --git a/pv/views/trace/signal.hpp b/pv/views/trace/signal.hpp index 1c53d8b..1b9f254 100644 --- a/pv/views/trace/signal.hpp +++ b/pv/views/trace/signal.hpp @@ -27,6 +27,8 @@ #include +#include + #include "trace.hpp" #include "viewitemowner.hpp" @@ -68,6 +70,15 @@ public: virtual shared_ptr 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 get_nearest_level_changes(uint64_t sample_pos) = 0; + /** * Returns true if the trace is visible and enabled. */ diff --git a/pv/views/trace/timeitem.cpp b/pv/views/trace/timeitem.cpp index bd07e9b..47a7da3 100644 --- a/pv/views/trace/timeitem.cpp +++ b/pv/views/trace/timeitem.cpp @@ -17,6 +17,7 @@ * along with this program; if not, see . */ +#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 diff --git a/pv/views/trace/timemarker.cpp b/pv/views/trace/timemarker.cpp index 65b9481..266007a 100644 --- a/pv/views/trace/timemarker.cpp +++ b/pv/views/trace/timemarker.cpp @@ -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 diff --git a/pv/views/trace/view.cpp b/pv/views/trace/view.cpp index 65b06ae..b987213 100644 --- a/pv/views/trace/view.cpp +++ b/pv/views/trace/view.cpp @@ -320,6 +320,11 @@ void View::remove_decode_signal(shared_ptr signal) } #endif +shared_ptr 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_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() / scale(); + const int64_t sample_num = max(((x_offset + p.x()) * samples_per_pixel), 0.0); + + // Query for nearest level changes + vector 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 s : signals_) { + const pair 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> trace_tree_items( list_by_type()); for (shared_ptr r : trace_tree_items) r->hover_point_changed(hover_point_); + // Notify any other listeners hover_point_changed(hover_point_); } diff --git a/pv/views/trace/view.hpp b/pv/views/trace/view.hpp index 8e78b62..9408218 100644 --- a/pv/views/trace/view.hpp +++ b/pv/views/trace/view.hpp @@ -129,6 +129,8 @@ public: virtual void remove_decode_signal(shared_ptr signal); #endif + shared_ptr 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 > trigger_markers_; QPoint hover_point_; + shared_ptr signal_under_mouse_cursor_; unsigned int sticky_events_; QTimer lazy_event_handler_; -- 2.30.2