]> sigrok.org Git - pulseview.git/blobdiff - pv/views/trace/view.cpp
TraceView: Implement view scroll margins
[pulseview.git] / pv / views / trace / view.cpp
index ae98dc3a85a50c30c377929dede032a81bbcb894..e5cf980a79223b1a84412fb1c845070194d4458d 100644 (file)
 #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>
@@ -84,7 +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;
@@ -98,8 +94,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)
@@ -124,7 +122,7 @@ bool CustomScrollArea::viewportEvent(QEvent *event)
        }
 }
 
-View::View(Session &session, bool is_main_view, QWidget *parent) :
+View::View(Session &session, bool is_main_view, QMainWindow *parent) :
        ViewBase(session, is_main_view, parent),
 
        // Note: Place defaults in View::reset_view_state(), not here
@@ -208,7 +206,7 @@ View::View(Session &session, bool is_main_view, QWidget *parent) :
 
        // 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,6 +226,24 @@ View::View(Session &session, bool is_main_view, QWidget *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,
+               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,
+               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,
+               nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
+       connect(cancel_grab_shortcut_, &QShortcut::activated,
+               this, [=]{grabbed_widget_ = nullptr;});
+       cancel_grab_shortcut_->setAutoRepeat(false);
+
        // Trigger the initial event manually. The default device has signals
        // which were created before this object came into being
        signals_changed();
@@ -244,6 +260,11 @@ View::~View()
        GlobalSettings::remove_change_handler(this);
 }
 
+ViewType View::get_type() const
+{
+       return ViewTypeTrace;
+}
+
 void View::reset_view_state()
 {
        ViewBase::reset_view_state();
@@ -253,6 +274,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;
@@ -265,6 +288,7 @@ void View::reset_view_state()
        next_flag_text_ = 'A';
        trigger_markers_.clear();
        hover_widget_ = nullptr;
+       grabbed_widget_ = nullptr;
        hover_point_ = QPoint(-1, -1);
        scroll_needs_defaults_ = true;
        saved_v_offset_ = 0;
@@ -298,9 +322,22 @@ unordered_set< shared_ptr<Signal> > View::signals() const
        return signals_;
 }
 
+shared_ptr<Signal> View::get_signal_by_signalbase(shared_ptr<data::SignalBase> base) const
+{
+       shared_ptr<Signal> ret_val;
+
+       for (const shared_ptr<Signal>& s : signals_)
+               if (s->base() == base) {
+                       ret_val = s;
+                       break;
+               }
+
+       return ret_val;
+}
+
 void View::clear_signals()
 {
-       ViewBase::clear_signalbases();
+       ViewBase::clear_signals();
        signals_.clear();
 }
 
@@ -319,11 +356,14 @@ void View::add_signal(const shared_ptr<Signal> signal)
 #ifdef ENABLE_DECODE
 void View::clear_decode_signals()
 {
+       ViewBase::clear_decode_signals();
        decode_traces_.clear();
 }
 
 void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
 {
+       ViewBase::add_decode_signal(signal);
+
        shared_ptr<DecodeTrace> d(
                new DecodeTrace(session_, signal, decode_traces_.size()));
        decode_traces_.push_back(d);
@@ -343,6 +383,8 @@ void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
                        signals_changed();
                        return;
                }
+
+       ViewBase::remove_decode_signal(signal);
 }
 #endif
 
@@ -371,6 +413,11 @@ const Viewport* View::viewport() const
        return viewport_;
 }
 
+QAbstractScrollArea* View::scrollarea() const
+{
+       return scrollarea_;
+}
+
 const Ruler* View::ruler() const
 {
        return ruler_;
@@ -385,12 +432,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());
@@ -408,20 +455,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());
 
@@ -501,6 +541,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);
@@ -511,6 +552,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();
@@ -629,12 +683,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();
 
@@ -798,26 +848,6 @@ pair<Timestamp, Timestamp> View::get_time_extents() const
        return make_pair(*left_time, *right_time);
 }
 
-void View::enable_show_sampling_points(bool state)
-{
-       (void)state;
-
-       viewport_->update();
-}
-
-void View::enable_show_analog_minor_grid(bool state)
-{
-       (void)state;
-
-       viewport_->update();
-}
-
-void View::enable_colored_bg(bool state)
-{
-       colored_bg_ = state;
-       viewport_->update();
-}
-
 bool View::colored_bg() const
 {
        return colored_bg_;
@@ -850,7 +880,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_);
 
@@ -1037,13 +1067,22 @@ int View::header_width() const
 
 void View::on_setting_changed(const QString &key, const QVariant &value)
 {
+       GlobalSettings settings;
+
+       if (key == GlobalSettings::Key_View_ColoredBG) {
+               colored_bg_ = settings.value(GlobalSettings::Key_View_ColoredBG).toBool();
+               viewport_->update();
+       }
+
+       if ((key == GlobalSettings::Key_View_ShowSamplingPoints) ||
+          (key == GlobalSettings::Key_View_ShowAnalogMinorGrid))
+               viewport_->update();
+
        if (key == GlobalSettings::Key_View_TriggerIsZeroTime)
                on_settingViewTriggerIsZeroTime_changed(value);
 
-       if (key == GlobalSettings::Key_View_SnapDistance) {
-               GlobalSettings settings;
+       if (key == GlobalSettings::Key_View_SnapDistance)
                snap_distance_ = settings.value(GlobalSettings::Key_View_SnapDistance).toInt();
-       }
 }
 
 void View::trigger_event(int segment_id, util::Timestamp location)
@@ -1052,15 +1091,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));
 }
@@ -1215,9 +1247,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();
@@ -1370,6 +1406,7 @@ void View::determine_time_unit()
 bool View::eventFilter(QObject *object, QEvent *event)
 {
        const QEvent::Type type = event->type();
+
        if (type == QEvent::MouseMove) {
 
                if (object)
@@ -1387,6 +1424,28 @@ bool View::eventFilter(QObject *object, QEvent *event)
 
                update_hover_point();
 
+               if (grabbed_widget_) {
+                       int64_t nearest = get_nearest_level_change(hover_point_);
+                       pv::util::Timestamp mouse_time = offset_ + hover_point_.x() * scale_;
+
+                       if (nearest == -1) {
+                               grabbed_widget_->set_time(mouse_time);
+                       } else {
+                               grabbed_widget_->set_time(nearest / get_signal_under_mouse_cursor()->base()->get_samplerate());
+                       }
+               }
+
+       } else if (type == QEvent::MouseButtonPress) {
+               grabbed_widget_ = nullptr;
+
+               const QMouseEvent *const mouse_event = (QMouseEvent*)event;
+               if ((object == viewport_) && (mouse_event->button() & Qt::LeftButton)) {
+                       // Send event to all trace tree items
+                       const vector<shared_ptr<TraceTreeItem>> trace_tree_items(
+                               list_by_type<TraceTreeItem>());
+                       for (const shared_ptr<TraceTreeItem>& r : trace_tree_items)
+                               r->mouse_left_press_event(mouse_event);
+               }
        } else if (type == QEvent::Leave) {
                hover_point_ = QPoint(-1, -1);
                update_hover_point();
@@ -1565,6 +1624,26 @@ void View::v_scroll_value_changed()
        viewport_->update();
 }
 
+void View::on_grab_ruler(int ruler_id)
+{
+       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()
 {
        using sigrok::Channel;
@@ -1752,7 +1831,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_;
@@ -1835,12 +1915,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();
 }