#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>
#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"
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;
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)
GlobalSettings::add_change_handler(this);
+ // Set up metadata objects and event handlers
+ if (is_main_view)
+ session_.metadata_obj_manager()->create_object(MetadataObjMainViewRange);
+
+
+ // Set up UI event handlers
connect(scrollarea_->horizontalScrollBar(), SIGNAL(valueChanged(int)),
this, SLOT(h_scroll_value_changed(int)));
connect(scrollarea_->verticalScrollBar(), SIGNAL(valueChanged(int)),
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,
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();
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;
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;
return session_;
}
-unordered_set< shared_ptr<Signal> > View::signals() const
+vector< shared_ptr<Signal> > View::signals() const
{
return 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_);
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());
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());
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();
}
}
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);
{
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();
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();
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
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;
}
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();
viewport_->update();
}
-void View::centre_cursors()
+void View::center_cursors()
{
assert(cursors_);
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));
}
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();
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
(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()
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;
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_;
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();
}