#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>
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;
}
}
-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
splitter_(new QSplitter()),
- header_was_shrunk_(false), // The splitter remains unchanged after a reset, so this goes here
- sticky_scrolling_(false) // Default setting is set in MainWindow::setup_ui()
+ header_was_shrunk_(false), // The splitter remains unchanged after a reset, so this goes here
+ sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui()
+ scroll_needs_defaults_(true)
{
QVBoxLayout *root_layout = new QVBoxLayout(this);
root_layout->setContentsMargins(0, 0, 0, 0);
splitter_->setHandleWidth(1); // Don't show a visible rubber band
splitter_->setCollapsible(0, false); // Prevent the header from collapsing
splitter_->setCollapsible(1, false); // Prevent the traces from collapsing
- splitter_->setStretchFactor(0, 0); // Prevent the panes from being resized
- splitter_->setStretchFactor(1, 1); // when the entire view is resized
+ splitter_->setStretchFactor(0, 0); // Prevent the panes from being resized
+ splitter_->setStretchFactor(1, 1); // when the entire view is resized
splitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
viewport_->installEventFilter(this);
this, SLOT(process_sticky_events()));
lazy_event_handler_.setSingleShot(true);
+ // Set up local keyboard shortcuts
+ zoom_in_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Plus), this,
+ 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_zoom_out_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
+ zoom_out_shortcut_->setAutoRepeat(false);
+
+ zoom_in_shortcut_2_ = new QShortcut(QKeySequence(Qt::Key_Up), this,
+ SLOT(on_zoom_in_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
+ zoom_out_shortcut_2_ = new QShortcut(QKeySequence(Qt::Key_Down), this,
+ SLOT(on_zoom_out_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
+
+ home_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Home), this,
+ SLOT(on_scroll_to_start_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut);
+ home_shortcut_->setAutoRepeat(false);
+
+ end_shortcut_ = new QShortcut(QKeySequence(Qt::Key_End), 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();
- // make sure the transparent widgets are on the top
+ // Make sure the transparent widgets are on the top
ruler_->raise();
header_->raise();
GlobalSettings::remove_change_handler(this);
}
+ViewType View::get_type() const
+{
+ return ViewTypeTrace;
+}
+
void View::reset_view_state()
{
ViewBase::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;
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 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();
}
#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);
signals_changed();
return;
}
+
+ ViewBase::remove_decode_signal(signal);
}
#endif
return viewport_;
}
+QAbstractScrollArea* View::scrollarea() const
+{
+ return scrollarea_;
+}
+
const Ruler* View::ruler() const
{
return ruler_;
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());
return items;
}
-shared_ptr<TimeItem> View::get_reference_time_item()
-{
- return ruler_->get_reference_item();
-}
-
double View::scale() const
{
return scale_;
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();
viewport_->update();
}
+void View::set_h_offset(int offset)
+{
+ scrollarea_->horizontalScrollBar()->setSliderPosition(offset);
+ header_->update();
+ viewport_->update();
+}
+
+int View::get_h_scrollbar_maximum() const
+{
+ return scrollarea_->horizontalScrollBar()->maximum();
+}
+
unsigned int View::depth() const
{
return 0;
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();
const Timestamp start_time = s->start_time();
left_time = left_time ?
min(*left_time, start_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;
+ start_time + d->max_sample_count() / samplerate;
}
}
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_;
viewport_->update();
}
-void View::centre_cursors()
+void View::center_cursors()
{
assert(cursors_);
shared_ptr<Flag> View::add_flag(const Timestamp& time)
{
- shared_ptr<Flag> flag = make_shared<Flag>(
- *this, time, QString("%1").arg(next_flag_text_));
+ shared_ptr<Flag> flag =
+ make_shared<Flag>(*this, time, QString("%1").arg(next_flag_text_));
flags_.push_back(flag);
next_flag_text_ = (next_flag_text_ >= 'Z') ? 'A' :
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)
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));
}
bool View::eventFilter(QObject *object, QEvent *event)
{
const QEvent::Type type = event->type();
+
if (type == QEvent::MouseMove) {
if (object)
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();
resize_header_to_fit();
}
+void View::on_zoom_in_shortcut_triggered()
+{
+ zoom(1);
+}
+
+void View::on_zoom_out_shortcut_triggered()
+{
+ zoom(-1);
+}
+
+void View::on_scroll_to_start_shortcut_triggered()
+{
+ set_h_offset(0);
+}
+
+void View::on_scroll_to_end_shortcut_triggered()
+{
+ set_h_offset(get_h_scrollbar_maximum());
+}
+
void View::h_scroll_value_changed(int value)
{
if (updating_scroll_)
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();
}