Fixes #627.
return samplerate_;
}
-double DecoderStack::start_time() const
+const pv::util::Timestamp& DecoderStack::start_time() const
{
return start_time_;
}
#include <pv/data/decode/row.hpp>
#include <pv/data/decode/rowdata.hpp>
+#include <pv/util.hpp>
struct srd_decoder;
struct srd_decoder_annotation_row;
double samplerate() const;
- double start_time() const;
+ const pv::util::Timestamp& start_time() const;
int64_t samples_decoded() const;
private:
pv::Session &session_;
- double start_time_;
+ pv::util::Timestamp start_time_;
double samplerate_;
/**
return sample_count_;
}
-double Segment::start_time() const
+const pv::util::Timestamp& Segment::start_time() const
{
return start_time_;
}
#ifndef PULSEVIEW_PV_DATA_SEGMENT_HPP
#define PULSEVIEW_PV_DATA_SEGMENT_HPP
+#include "pv/util.hpp"
+
#include <thread>
#include <mutex>
#include <vector>
uint64_t get_sample_count() const;
- double start_time() const;
+ const pv::util::Timestamp& start_time() const;
double samplerate() const;
void set_samplerate(double samplerate);
mutable std::recursive_mutex mutex_;
std::vector<uint8_t> data_;
uint64_t sample_count_;
- double start_time_;
+ pv::util::Timestamp start_time_;
double samplerate_;
uint64_t capacity_;
unsigned int unit_size_;
{
if ((watched == &sample_count_ || watched == &sample_rate_) &&
(event->type() == QEvent::ToolTip)) {
- double sec = (double)sample_count_.value() / sample_rate_.value();
+ auto sec = pv::util::Timestamp(sample_count_.value()) / sample_rate_.value();
QHelpEvent *help_event = static_cast<QHelpEvent*>(event);
QString str = tr("Total sampling time: %1").arg(pv::util::format_second(sec));
const int FirstSIPrefixPower = -(FirstSIPrefix * 3);
const double MinTimeDelta = 1e-15; // Anything below 1 fs can be considered zero
-QString format_si_value(double v, QString unit, int prefix,
+static QString format_si_value(double v, QString unit, int prefix,
unsigned int precision, bool sign)
{
if (prefix < 0) {
return s;
}
+QString format_si_value(const Timestamp& v, QString unit, int prefix,
+ unsigned int precision, bool sign)
+{
+ return format_si_value(v.convert_to<double>(), unit, prefix, precision, sign);
+}
+
static QString pad_number(unsigned int number, int length)
{
return QString("%1").arg(number, length, 10, QChar('0'));
return format_si_value(t, unit, prefix, relative_prec);
}
-QString format_time(double t, int prefix, TimeUnit unit, unsigned int precision)
+static QString format_time(double t, int prefix, TimeUnit unit, unsigned int precision)
{
// Make 0 appear as 0, not random +0 or -0
if (fabs(t) < MinTimeDelta)
return format_time_in_full(t, precision);
}
-QString format_second(double second)
+QString format_time(const Timestamp& t, int prefix, TimeUnit unit, unsigned int precision)
+{
+ return format_time(t.convert_to<double>(), prefix, unit, precision);
+}
+
+QString format_second(const Timestamp& second)
{
- return format_si_value(second, "s", -1, 0, false);
+ return format_si_value(second.convert_to<double>(), "s", -1, 0, false);
}
} // namespace util
#include <cmath>
+#include <boost/multiprecision/cpp_dec_float.hpp>
+
#include <QString>
namespace pv {
Samples = 2
};
+/// Timestamp type providing yoctosecond resolution.
+typedef boost::multiprecision::number<
+ boost::multiprecision::cpp_dec_float<24>,
+ boost::multiprecision::et_off> Timestamp;
+
extern const int FirstSIPrefixPower;
/**
* Formats a given value with the specified SI prefix.
* @param v The value to format.
* @param unit The unit of quantity.
- * @param prefix The number of the prefix, from 0 for 'femto' up to
- * 8 for 'giga'. If prefix is set to -1, the prefix will be calculated.
+ * @param prefix The number of the prefix, from 0 for 'yotta' up to
+ * 16 for 'yokto'. If prefix is set to -1, the prefix will be calculated.
* @param precision The number of digits after the decimal separator.
* @param sign Whether or not to add a sign also for positive numbers.
*
* @return The formated value.
*/
QString format_si_value(
- double v, QString unit, int prefix = -1,
+ const Timestamp& v, QString unit, int prefix = -1,
unsigned precision = 0, bool sign = true);
/**
* Formats a given time with the specified SI prefix.
* @param t The time value in seconds to format.
- * @param prefix The number of the prefix, from 0 for 'femto' up to
- * 8 for 'giga'. If prefix is set to -1, the prefix will be calculated.
+ * @param prefix The number of the prefix, from 0 for 'yotta' up to
+ * 16 for 'yokto'. If prefix is set to -1, the prefix will be calculated.
* @param unit The unit of quantity.
* @param precision The number of digits after the decimal separator or period (.).
*
* @return The formated value.
*/
QString format_time(
- double t, int prefix = -1, TimeUnit unit = Time, unsigned precision = 0);
+ const Timestamp& t, int prefix = -1,
+ TimeUnit unit = Time, unsigned precision = 0);
/**
* Formats a given time value with a SI prefix so that the
*
* @return The formated value.
*/
-QString format_second(double second);
+QString format_second(const Timestamp& second);
} // namespace util
} // namespace pv
const double pixels_offset = pp.pixels_offset();
const double samplerate = segment->samplerate();
- const double start_time = segment->start_time();
+ const pv::util::Timestamp& start_time = segment->start_time();
const int64_t last_sample = segment->get_sample_count() - 1;
const double samples_per_pixel = samplerate * pp.scale();
- const double start = samplerate * (pp.offset() - start_time);
- const double end = start + samples_per_pixel * pp.width();
+ const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
+ const pv::util::Timestamp end = start + samples_per_pixel * pp.width();
- const int64_t start_sample = min(max((int64_t)floor(start),
+ const int64_t start_sample = min(max(floor(start).convert_to<int64_t>(),
(int64_t)0), last_sample);
- const int64_t end_sample = min(max((int64_t)ceil(end) + 1,
+ const int64_t end_sample = min(max((ceil(end) + 1).convert_to<int64_t>(),
(int64_t)0), last_sample);
if (samples_per_pixel < EnvelopeThreshold)
const shared_ptr<Cursor> other(get_other_cursor());
assert(other);
- const float x = (time_ - view_.offset()) / view_.scale();
+ const float x = ((time_ - view_.offset())/ view_.scale()).convert_to<float>();
QFontMetrics m(QApplication::font());
QSize text_size = m.boundingRect(get_text()).size();
TimeMarker::ArrowSize - 0.5f;
const float height = label_size.height();
- const double other_time = other->time();
+ const pv::util::Timestamp& other_time = other->time();
+
if (time_ > other_time ||
- (abs(time_ - other_time) < numeric_limits<double>::epsilon() &&
- this > other.get()))
+ (abs(time_ - other_time).is_zero() && this > other.get()))
return QRectF(x, top, label_size.width(), height);
else
- return QRectF(x - label_size.width(), top,
- label_size.width(), height);
+ return QRectF(x - label_size.width(), top, label_size.width(), height);
}
shared_ptr<Cursor> Cursor::get_other_cursor() const
return second_;
}
-void CursorPair::set_time(double time) {
- const double delta = second_->time() - first_->time();
+void CursorPair::set_time(const pv::util::Timestamp& time) {
+ const pv::util::Timestamp delta = second_->time() - first_->time();
first_->set_time(time);
second_->set_time(time + delta);
}
QString CursorPair::format_string()
{
const unsigned int prefix = view_.tick_prefix();
- const double delta = second_->time() - first_->time();
+ const pv::util::Timestamp delta = second_->time() - first_->time();
return QString("%1 / %2").
arg(util::format_time(delta, prefix, view_.time_unit(), 2)).
arg(util::format_si_value(1.0 / fabs(delta), "Hz", -1, 4));
assert(second_);
return pair<float, float>(
- (first_->time() - view_.offset()) / view_.scale(),
- (second_->time() - view_.offset()) / view_.scale());
+ ((first_->time() - view_.offset()) / view_.scale()).convert_to<float>(),
+ ((second_->time() - view_.offset()) / view_.scale()).convert_to<float>());
}
} // namespace view
/**
* Sets the time of the marker.
*/
- void set_time(double time);
+ void set_time(const pv::util::Timestamp& time) override;
float get_x() const;
assert(scale > 0);
const double pixels_offset =
- (view->offset() - decoder_stack_->start_time()) / scale;
+ ((view->offset() - decoder_stack_->start_time()) / scale).convert_to<double>();
double samplerate = decoder_stack_->samplerate();
const QColor Flag::FillColour(0x73, 0xD2, 0x16);
-Flag::Flag(View &view, double time, const QString &text) :
+Flag::Flag(View &view, const pv::util::Timestamp& time, const QString &text) :
TimeMarker(view, FillColour, time),
text_(text)
{
* @param time The time to set the flag to.
* @param text The text of the marker.
*/
- Flag(View &view, double time, const QString &text);
+ Flag(View &view, const pv::util::Timestamp& time, const QString &text);
/**
* Copy constructor.
samplerate = 1.0;
const double pixels_offset = pp.pixels_offset();
- const double start_time = segment->start_time();
+ const pv::util::Timestamp& start_time = segment->start_time();
const int64_t last_sample = segment->get_sample_count() - 1;
const double samples_per_pixel = samplerate * pp.scale();
- const double start = samplerate * (pp.offset() - start_time);
- const double end = start + samples_per_pixel * pp.width();
+ const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
+ const pv::util::Timestamp end = start + samples_per_pixel * pp.width();
- segment->get_subsampled_edges(edges,
- min(max((int64_t)floor(start), (int64_t)0), last_sample),
- min(max((int64_t)ceil(end), (int64_t)0), last_sample),
+ const int64_t start_sample = min(max(floor(start).convert_to<int64_t>(),
+ (int64_t)0), last_sample);
+ const uint64_t end_sample = min(max(ceil(end).convert_to<int64_t>(),
+ (int64_t)0), last_sample);
+
+ segment->get_subsampled_edges(edges, start_sample, end_sample,
samples_per_pixel / Oversampling, channel_->index());
assert(edges.size() >= 2);
const double minor_tick_period = tick_period / MinorTickSubdivision;
const double first_major_division =
- floor(view_.offset() / tick_period);
+ floor(view_.offset() / tick_period).convert_to<double>();
const double first_minor_division =
- ceil(view_.offset() / minor_tick_period);
+ ceil(view_.offset() / minor_tick_period).convert_to<double>();
const double t0 = first_major_division * tick_period;
int division = (int)round(first_minor_division -
do {
const double t = t0 + division * minor_tick_period;
- x = (t - view_.offset()) / view_.scale();
+ x = ((t - view_.offset()) / view_.scale()).convert_to<double>();
if (division % MinorTickSubdivision == 0)
{
/**
* Sets the time of the marker.
*/
- virtual void set_time(double time) = 0;
+ virtual void set_time(const pv::util::Timestamp& time) = 0;
virtual float get_x() const = 0;
const int TimeMarker::ArrowSize = 4;
-TimeMarker::TimeMarker(View &view, const QColor &colour, double time) :
+TimeMarker::TimeMarker(
+ View &view, const QColor &colour, const pv::util::Timestamp& time) :
TimeItem(view),
colour_(colour),
time_(time),
{
}
-double TimeMarker::time() const
+const pv::util::Timestamp& TimeMarker::time() const
{
return time_;
}
-void TimeMarker::set_time(double time)
+void TimeMarker::set_time(const pv::util::Timestamp& time)
{
time_ = time;
if (value_widget_) {
updating_value_widget_ = true;
- value_widget_->setValue(time);
+ value_widget_->setValue(time.convert_to<double>());
updating_value_widget_ = false;
}
float TimeMarker::get_x() const
{
- return (time_ - view_.offset()) / view_.scale();
+ return ((time_ - view_.offset()) / view_.scale()).convert_to<float>();
}
QPoint TimeMarker::point(const QRect &rect) const
if (!enabled())
return;
- const qreal x = (time_ - view_.offset()) / view_.scale();
+ const qreal x = ((time_ - view_.offset()) / view_.scale()).convert_to<qreal>();
const QRectF r(label_rect(rect));
const QPointF points[] = {
value_widget_->setSuffix("s");
value_widget_->setSingleStep(1e-6);
value_widget_->setRange(-1.0e9, 1.0e9);
- value_widget_->setValue(time_);
+ value_widget_->setValue(time_.convert_to<double>());
connect(value_widget_, SIGNAL(valueChanged(double)),
this, SLOT(on_value_changed(double)));
* @param colour A reference to the colour of this cursor.
* @param time The time to set the flag to.
*/
- TimeMarker(View &view, const QColor &colour, double time);
+ TimeMarker(View &view, const QColor &colour, const pv::util::Timestamp& time);
public:
/**
* Gets the time of the marker.
*/
- double time() const;
+ const pv::util::Timestamp& time() const;
/**
* Sets the time of the marker.
*/
- void set_time(double time);
+ void set_time(const pv::util::Timestamp& time) override;
float get_x() const;
protected:
const QColor &colour_;
- double time_;
+ pv::util::Timestamp time_;
QSizeF text_size_;
using pv::data::Segment;
using pv::util::format_time;
using pv::util::TimeUnit;
+using pv::util::Timestamp;
using std::deque;
using std::dynamic_pointer_cast;
namespace pv {
namespace view {
-const double View::MaxScale = 1e9;
-const double View::MinScale = 1e-12;
+const Timestamp View::MaxScale("1e9");
+const Timestamp View::MinScale("1e-12");
const int View::MaxScrollValue = INT_MAX / 2;
const int View::MaxViewAutoUpdateRate = 25; // No more than 25 Hz with sticky scrolling
return scale_;
}
-double View::offset() const
+const Timestamp& View::offset() const
{
return offset_;
}
always_zoom_to_fit_changed(gui_state);
}
- const pair<double, double> extents = get_time_extents();
- const double delta = extents.second - extents.first;
- if (delta < 1e-12)
+ const pair<Timestamp, Timestamp> extents = get_time_extents();
+ const Timestamp delta = extents.second - extents.first;
+ if (delta < Timestamp("1e-12"))
return;
assert(viewport_);
if (w <= 0)
return;
- const double scale = max(min(delta / w, MaxScale), MinScale);
- set_scale_offset(scale, extents.first);
+ const Timestamp scale = max(min(delta / w, MaxScale), MinScale);
+ set_scale_offset(scale.convert_to<double>(), extents.first);
}
void View::zoom_one_to_one()
set_zoom(1.0 / samplerate, w / 2);
}
-void View::set_scale_offset(double scale, double offset)
+void View::set_scale_offset(double scale, const Timestamp& offset)
{
// Disable sticky scrolling / always zoom to fit when acquisition runs
// and user drags the viewport
return visible_data;
}
-pair<double, double> View::get_time_extents() const
+pair<Timestamp, Timestamp> View::get_time_extents() const
{
- double left_time = DBL_MAX, right_time = DBL_MIN;
+ 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)
{
double samplerate = s->samplerate();
samplerate = (samplerate <= 0.0) ? 1.0 : samplerate;
- const double start_time = s->start_time();
- left_time = min(left_time, start_time);
- right_time = max(right_time, start_time +
- d->max_sample_count() / samplerate);
+ const Timestamp start_time = s->start_time();
+ left_time = left_time ?
+ min(*left_time, start_time) :
+ start_time;
+ right_time = right_time ?
+ max(*right_time, start_time + d->max_sample_count() / samplerate) :
+ start_time + d->max_sample_count() / samplerate;
}
}
- if (left_time == DBL_MAX && right_time == DBL_MIN)
- return make_pair(0.0, 0.0);
+ if (!left_time || !right_time)
+ return make_pair(0, 0);
- assert(left_time < right_time);
- return make_pair(left_time, right_time);
+ assert(*left_time < *right_time);
+ return make_pair(*left_time, *right_time);
}
void View::enable_sticky_scrolling(bool state)
return cursors_;
}
-void View::add_flag(double time)
+void View::add_flag(const Timestamp& time)
{
flags_.push_back(shared_ptr<Flag>(new Flag(*this, time,
QString("%1").arg(next_flag_text_))));
r->animate_to_layout_v_offset();
}
-void View::get_scroll_layout(double &length, double &offset) const
+void View::get_scroll_layout(double &length, Timestamp &offset) const
{
- const pair<double, double> extents = get_time_extents();
- length = (extents.second - extents.first) / scale_;
+ const pair<Timestamp, Timestamp> extents = get_time_extents();
+ length = ((extents.second - extents.first) / scale_).convert_to<double>();
offset = offset_ / scale_;
}
always_zoom_to_fit_ = false;
always_zoom_to_fit_changed(false);
- const double cursor_offset = offset_ + scale_ * offset;
- const double new_scale = max(min(scale, MaxScale), MinScale);
- const double new_offset = cursor_offset - new_scale * offset;
- set_scale_offset(new_scale, new_offset);
+ const Timestamp cursor_offset = offset_ + scale_ * offset;
+ const Timestamp new_scale = max(min(Timestamp(scale), MaxScale), MinScale);
+ const Timestamp new_offset = cursor_offset - new_scale * offset;
+ set_scale_offset(new_scale.convert_to<double>(), new_offset);
}
void View::calculate_tick_spacing()
// Figure out the highest numeric value visible on a label
const QSize areaSize = viewport_->size();
- const double max_time = max(fabs(offset_),
+ const Timestamp max_time = max(fabs(offset_),
fabs(offset_ + scale_ * areaSize.width()));
double min_width = SpacingIncrement;
const QSize areaSize = viewport_->size();
// Set the horizontal scroll bar
- double length = 0, offset = 0;
+ double length = 0;
+ Timestamp offset;
get_scroll_layout(length, offset);
length = max(length - areaSize.width(), 0.0);
if (length < MaxScrollValue) {
horizontalScrollBar()->setRange(0, length);
- horizontalScrollBar()->setSliderPosition(offset);
+ horizontalScrollBar()->setSliderPosition(offset.convert_to<double>());
} else {
horizontalScrollBar()->setRange(0, MaxScrollValue);
horizontalScrollBar()->setSliderPosition(
- offset_ * MaxScrollValue / (scale_ * length));
+ (offset_ * MaxScrollValue / (scale_ * length)).convert_to<double>());
}
updating_scroll_ = false;
if (range < MaxScrollValue)
offset_ = scale_ * value;
else {
- double length = 0, offset;
+ double length = 0;
+ Timestamp offset;
get_scroll_layout(length, offset);
offset_ = scale_ * length * value / MaxScrollValue;
}
if (sticky_scrolling_) {
// Make right side of the view sticky
- double length = 0, offset;
+ double length = 0;
+ Timestamp offset;
get_scroll_layout(length, offset);
const QSize areaSize = viewport_->size();
};
private:
- static const double MaxScale;
- static const double MinScale;
+ static const pv::util::Timestamp MaxScale;
+ static const pv::util::Timestamp MinScale;
static const int MaxScrollValue;
static const int MaxViewAutoUpdateRate;
* Returns the time offset of the left edge of the view in
* seconds.
*/
- double offset() const;
+ const pv::util::Timestamp& offset() const;
/**
* Returns the vertical scroll offset.
* @param scale The new view scale in seconds per pixel.
* @param offset The view time offset in seconds.
*/
- void set_scale_offset(double scale, double offset);
+ void set_scale_offset(double scale, const pv::util::Timestamp& offset);
std::set< std::shared_ptr<pv::data::SignalData> >
get_visible_data() const;
- std::pair<double, double> get_time_extents() const;
+ std::pair<pv::util::Timestamp, pv::util::Timestamp> get_time_extents() const;
/**
* Enables or disables sticky scrolling, i.e. the view always shows
/**
* Adds a new flag at a specified time.
*/
- void add_flag(double time);
+ void add_flag(const pv::util::Timestamp& time);
/**
* Removes a flag from the list.
void always_zoom_to_fit_changed(bool state);
private:
- void get_scroll_layout(double &length, double &offset) const;
+ void get_scroll_layout(double &length, pv::util::Timestamp &offset) const;
/**
* Simultaneously sets the zoom and offset.
double scale_;
/// The view time offset in seconds.
- double offset_;
+ pv::util::Timestamp offset_;
bool updating_scroll_;
bool sticky_scrolling_;
namespace view {
ViewItemPaintParams::ViewItemPaintParams(
- const QRect &rect, double scale, double offset) :
+ const QRect &rect, double scale, const pv::util::Timestamp& offset) :
rect_(rect),
scale_(scale),
offset_(offset) {
#ifndef PULSEVIEW_PV_VIEW_ROWITEMPAINTPARAMS_HPP
#define PULSEVIEW_PV_VIEW_ROWITEMPAINTPARAMS_HPP
+#include "pv/util.hpp"
+
#include <QFont>
namespace pv {
class ViewItemPaintParams
{
public:
- ViewItemPaintParams(const QRect &rect, double scale, double offset);
+ ViewItemPaintParams(
+ const QRect &rect, double scale, const pv::util::Timestamp& offset);
QRect rect() const {
return rect_;
return scale_;
}
- double offset() const {
+ const pv::util::Timestamp& offset() const {
return offset_;
}
}
double pixels_offset() const {
- return offset_ / scale_;
+ return (offset_ / scale_).convert_to<double>();
}
public:
private:
QRect rect_;
double scale_;
- double offset_;
+ pv::util::Timestamp offset_;
};
} // namespace view
Viewport::Viewport(View &parent) :
ViewWidget(parent),
- drag_offset_(numeric_limits<double>::signaling_NaN()),
pinch_zoom_active_(false)
{
setAutoFillBackground(true);
void Viewport::drag_by(const QPoint &delta)
{
- // Use std::isnan() instead of isnan(), the latter can cause issues.
- if (std::isnan(drag_offset_))
+ if (drag_offset_ == boost::none)
return;
- view_.set_scale_offset(view_.scale(), drag_offset_ -
- delta.x() * view_.scale());
+ view_.set_scale_offset(view_.scale(),
+ (*drag_offset_ - delta.x() * view_.scale()));
}
void Viewport::drag_release()
{
- drag_offset_ = numeric_limits<double>::signaling_NaN();
+ drag_offset_ = boost::none;
}
vector< shared_ptr<ViewItem> > Viewport::items()
if (!pinch_zoom_active_ ||
(event->touchPointStates() & Qt::TouchPointPressed)) {
- pinch_offset0_ = view_.offset() + view_.scale() * touchPoint0.pos().x();
- pinch_offset1_ = view_.offset() + view_.scale() * touchPoint1.pos().x();
+ pinch_offset0_ = (view_.offset() + view_.scale() * touchPoint0.pos().x()).convert_to<double>();
+ pinch_offset1_ = (view_.offset() + view_.scale() * touchPoint1.pos().x()).convert_to<double>();
pinch_zoom_active_ = true;
}
#ifndef PULSEVIEW_PV_VIEW_VIEWPORT_HPP
#define PULSEVIEW_PV_VIEW_VIEWPORT_HPP
+#include <boost/optional.hpp>
+
#include <QTimer>
#include <QTouchEvent>
+#include "pv/util.hpp"
#include "viewwidget.hpp"
class QPainter;
void wheelEvent(QWheelEvent *event);
private:
- double drag_offset_;
+ boost::optional<pv::util::Timestamp> drag_offset_;
double pinch_offset0_;
double pinch_offset1_;