pulseview-0.4.1/pv/000700 001750 001750 00000000000 13365562011 013616 5ustar00uweuwe000000 000000 pulseview-0.4.1/pv/strnatcmp.hpp000600 001750 001750 00000007162 13365561737 016370 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2000, 2004 by Martin Pool * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ // strnatcmp.c -- Perform 'natural order' comparisons of strings in C. // This file has been modified for C++ compatibility. // Original at https://github.com/sourcefrog/natsort/blob/master/strnatcmp.c #ifndef PULSEVIEW_PV_STRNATCMP_HPP #define PULSEVIEW_PV_STRNATCMP_HPP #include #include /* size_t */ #include using std::string; static int compare_right(char const *a, char const *b) { int bias = 0; // The longest run of digits wins. That aside, the greatest // value wins, but we can't know that it will until we've scanned // both numbers to know that they have the same magnitude, so we // remember it in bias. for (;; a++, b++) { if (!isdigit(*a) && !isdigit(*b)) return bias; if (!isdigit(*a)) return -1; if (!isdigit(*b)) return +1; if (*a < *b) { if (!bias) bias = -1; } else if (*a > *b) { if (!bias) bias = +1; } else if (!*a && !*b) return bias; } return 0; } static int compare_left(char const *a, char const *b) { // Compare two left-aligned numbers: the first to have a // different value wins. for (;; a++, b++) { if (!isdigit(*a) && !isdigit(*b)) return 0; if (!isdigit(*a)) return -1; if (!isdigit(*b)) return +1; if (*a < *b) return -1; if (*a > *b) return +1; } return 0; } static int strnatcmp0(char const *a, char const *b, int fold_case) { int ai, bi, fractional, result; char ca, cb; ai = bi = 0; while (true) { ca = a[ai]; cb = b[bi]; // Skip over leading spaces or zeroes while (isspace(ca)) ca = a[++ai]; while (isspace(cb)) cb = b[++bi]; // Process run of digits if (isdigit(ca) && isdigit(cb)) { fractional = (ca == '0' || cb == '0'); if (fractional) { if ((result = compare_left(a + ai, b + bi)) != 0) return result; } else { if ((result = compare_right(a + ai, b + bi)) != 0) return result; } } if (!ca && !cb) { // The strings compare the same. Perhaps the caller // will want to call strcmp to break the tie return 0; } if (fold_case) { ca = toupper(ca); cb = toupper(cb); } if (ca < cb) return -1; if (ca > cb) return +1; ++ai; ++bi; } } // Compare, recognizing numeric strings and being case sensitive int strnatcmp(char const *a, char const *b) { return strnatcmp0(a, b, 0); } int strnatcmp(const string a, const string b) { return strnatcmp0(a.c_str(), b.c_str(), 0); } // Compare, recognizing numeric strings and ignoring case int strnatcasecmp(char const *a, char const *b) { return strnatcmp0(a, b, 1); } int strnatcasecmp(const string a, const string b) { return strnatcmp0(a.c_str(), b.c_str(), 1); } #endif // PULSEVIEW_PV_STRNATCMP_HPP pulseview-0.4.1/pv/application.hpp000600 001750 001750 00000003704 13365561737 016656 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Martin Ling * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_APPLICATION_HPP #define PULSEVIEW_PV_APPLICATION_HPP #include #include #include using std::shared_ptr; using std::pair; using std::vector; class Application : public QApplication { Q_OBJECT public: Application(int &argc, char* argv[]); void collect_version_info(shared_ptr context); void print_version_info(); vector< pair > get_version_info() const; vector get_fw_path_list() const; vector get_pd_path_list() const; vector< pair > get_driver_list() const; vector< pair > get_input_format_list() const; vector< pair > get_output_format_list() const; vector< pair > get_pd_list() const; private: bool notify(QObject *receiver, QEvent *event); vector< pair > version_info_; vector fw_path_list_; vector pd_path_list_; vector< pair > driver_list_; vector< pair > input_format_list_; vector< pair > output_format_list_; vector< pair > pd_list_; }; #endif // PULSEVIEW_PV_APPLICATION_HPP pulseview-0.4.1/pv/views/000700 001750 001750 00000000000 13365562011 014753 5ustar00uweuwe000000 000000 pulseview-0.4.1/pv/views/viewbase.cpp000600 001750 001750 00000010072 13365561737 017304 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * Copyright (C) 2016 Soeren Apel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifdef ENABLE_DECODE #include #endif #include #include "pv/session.hpp" #include "pv/util.hpp" #include "pv/data/segment.hpp" using std::shared_ptr; namespace pv { namespace views { const int ViewBase::MaxViewAutoUpdateRate = 25; // No more than 25 Hz ViewBase::ViewBase(Session &session, bool is_main_view, QWidget *parent) : // Note: Place defaults in ViewBase::reset_view_state(), not here session_(session), is_main_view_(is_main_view) { (void)parent; connect(&session_, SIGNAL(signals_changed()), this, SLOT(signals_changed())); connect(&session_, SIGNAL(capture_state_changed(int)), this, SLOT(capture_state_updated(int))); connect(&session_, SIGNAL(new_segment(int)), this, SLOT(on_new_segment(int))); connect(&delayed_view_updater_, SIGNAL(timeout()), this, SLOT(perform_delayed_view_update())); delayed_view_updater_.setSingleShot(true); delayed_view_updater_.setInterval(1000 / MaxViewAutoUpdateRate); } void ViewBase::reset_view_state() { ruler_shift_ = 0; current_segment_ = 0; } Session& ViewBase::session() { return session_; } const Session& ViewBase::session() const { return session_; } void ViewBase::clear_signals() { } unordered_set< shared_ptr > ViewBase::signalbases() const { return signalbases_; } void ViewBase::clear_signalbases() { for (const shared_ptr& signalbase : signalbases_) { disconnect(signalbase.get(), SIGNAL(samples_cleared()), this, SLOT(on_data_updated())); disconnect(signalbase.get(), SIGNAL(samples_added(uint64_t, uint64_t, uint64_t)), this, SLOT(on_samples_added(uint64_t, uint64_t, uint64_t))); } signalbases_.clear(); } void ViewBase::add_signalbase(const shared_ptr signalbase) { signalbases_.insert(signalbase); connect(signalbase.get(), SIGNAL(samples_cleared()), this, SLOT(on_data_updated())); connect(signalbase.get(), SIGNAL(samples_added(uint64_t, uint64_t, uint64_t)), this, SLOT(on_samples_added(uint64_t, uint64_t, uint64_t))); } #ifdef ENABLE_DECODE void ViewBase::clear_decode_signals() { } void ViewBase::add_decode_signal(shared_ptr signal) { (void)signal; } void ViewBase::remove_decode_signal(shared_ptr signal) { (void)signal; } #endif void ViewBase::save_settings(QSettings &settings) const { (void)settings; } void ViewBase::restore_settings(QSettings &settings) { (void)settings; } void ViewBase::trigger_event(int segment_id, util::Timestamp location) { (void)segment_id; (void)location; } void ViewBase::signals_changed() { } void ViewBase::on_new_segment(int new_segment_id) { (void)new_segment_id; } void ViewBase::on_segment_completed(int new_segment_id) { (void)new_segment_id; } void ViewBase::capture_state_updated(int state) { (void)state; } void ViewBase::perform_delayed_view_update() { } void ViewBase::on_samples_added(uint64_t segment_id, uint64_t start_sample, uint64_t end_sample) { (void)start_sample; (void)end_sample; if (segment_id != current_segment_) return; if (!delayed_view_updater_.isActive()) delayed_view_updater_.start(); } void ViewBase::on_data_updated() { if (!delayed_view_updater_.isActive()) delayed_view_updater_.start(); } } // namespace views } // namespace pv pulseview-0.4.1/pv/views/viewbase.hpp000600 001750 001750 00000006255 13365561737 017321 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * Copyright (C) 2016 Soeren Apel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_VIEWBASE_HPP #define PULSEVIEW_PV_VIEWS_VIEWBASE_HPP #include #include #include #include #include #include #include #include #ifdef ENABLE_DECODE #include #endif using std::shared_ptr; using std::unordered_set; namespace pv { class Session; namespace view { class DecodeTrace; class Signal; } namespace views { enum ViewType { ViewTypeTrace, ViewTypeTabularDecode }; class ViewBase : public QWidget { Q_OBJECT private: static const int MaxViewAutoUpdateRate; public: explicit ViewBase(Session &session, bool is_main_view = false, QWidget *parent = nullptr); /** * Resets the view to its default state after construction. It does however * not reset the signal bases or any other connections with the session. */ virtual void reset_view_state(); Session& session(); const Session& session() const; virtual void clear_signals(); /** * Returns the signal bases contained in this view. */ unordered_set< shared_ptr > signalbases() const; virtual void clear_signalbases(); virtual void add_signalbase(const shared_ptr signalbase); #ifdef ENABLE_DECODE virtual void clear_decode_signals(); virtual void add_decode_signal(shared_ptr signal); virtual void remove_decode_signal(shared_ptr signal); #endif virtual void save_settings(QSettings &settings) const; virtual void restore_settings(QSettings &settings); public Q_SLOTS: virtual void trigger_event(int segment_id, util::Timestamp location); virtual void signals_changed(); virtual void capture_state_updated(int state); virtual void on_new_segment(int new_segment_id); virtual void on_segment_completed(int new_segment_id); virtual void perform_delayed_view_update(); private Q_SLOTS: void on_samples_added(uint64_t segment_id, uint64_t start_sample, uint64_t end_sample); void on_data_updated(); protected: Session &session_; const bool is_main_view_; util::Timestamp ruler_shift_; util::TimeUnit time_unit_; unordered_set< shared_ptr > signalbases_; /// The ID of the currently displayed segment uint32_t current_segment_; QTimer delayed_view_updater_; }; } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_VIEWBASE_HPP pulseview-0.4.1/pv/views/trace/000700 001750 001750 00000000000 13365562011 016051 5ustar00uweuwe000000 000000 pulseview-0.4.1/pv/views/trace/signal.hpp000600 001750 001750 00000005653 13365561737 020070 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNAL_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNAL_HPP #include #include #include #include #include #include "trace.hpp" #include "viewitemowner.hpp" using std::shared_ptr; namespace pv { class Session; namespace data { class SignalBase; class SignalData; } namespace views { namespace trace { /** * The Signal class represents a series of numeric values that can be drawn. * This is the main difference to the more generic @ref Trace class. * * It is generally accepted that Signal instances consider themselves to be * individual channels on e.g. an oscilloscope, though it should be kept in * mind that virtual signals (e.g. math) will also be served by the Signal * class. */ class Signal : public Trace, public ViewItemOwner { Q_OBJECT protected: Signal(pv::Session &session, shared_ptr channel); public: /** * Sets the name of the signal. */ virtual void set_name(QString name); 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. */ bool enabled() const; shared_ptr base() const; virtual void save_settings(QSettings &settings) const; virtual void restore_settings(QSettings &settings); void paint_back(QPainter &p, ViewItemPaintParams &pp); virtual void populate_popup_form(QWidget *parent, QFormLayout *form); QMenu* create_header_context_menu(QWidget *parent); void delete_pressed(); protected Q_SLOTS: virtual void on_name_changed(const QString &text); void on_disable(); void on_enabled_changed(bool enabled); protected: pv::Session &session_; QComboBox *name_widget_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_SIGNAL_HPP pulseview-0.4.1/pv/views/trace/trace.hpp000600 001750 001750 00000013246 13365561737 017706 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACE_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACE_HPP #include #include #include #include #include #include #include "tracetreeitem.hpp" #include #include "pv/data/signalbase.hpp" using std::shared_ptr; class QFormLayout; namespace pv { namespace data { class SignalBase; } namespace widgets { class Popup; } namespace views { namespace trace { /** * The Trace class represents a @ref TraceTreeItem which occupies some vertical * space on the canvas and spans across its entire width, essentially showing * a time series of values, events, objects or similar. While easily confused * with @ref Signal, the difference is that Trace may represent anything that * can be drawn, not just numeric values. One example is a @ref DecodeTrace. * * For this reason, Trace is more generic and contains properties and helpers * that benefit any kind of time series items. */ class Trace : public TraceTreeItem, public GlobalSettingsInterface { Q_OBJECT public: /** * Allowed values for the multi-segment display mode. * * Note: Consider these locations when updating the list: * * * @ref View::set_segment_display_mode * @ref View::on_segment_changed * @ref AnalogSignal::get_analog_segment_to_paint * @ref AnalogSignal::get_logic_segment_to_paint * @ref LogicSignal::get_logic_segment_to_paint * @ref StandardBar */ enum SegmentDisplayMode { ShowLastSegmentOnly = 1, ShowLastCompleteSegmentOnly, ShowSingleSegmentOnly, ShowAllSegments, ShowAccumulatedIntensity }; private: static const QPen AxisPen; static const int LabelHitPadding; static const QColor BrightGrayBGColor; static const QColor DarkGrayBGColor; protected: Trace(shared_ptr channel); ~Trace(); public: /** * Returns the underlying SignalBase instance. */ shared_ptr base() const; /** * Returns true if the item may be selected. */ virtual bool is_selectable(QPoint pos) const; /** * Returns true if the item may be dragged/moved. */ virtual bool is_draggable(QPoint pos) const; /** * Configures the segment display mode to use. */ virtual void set_segment_display_mode(SegmentDisplayMode mode); virtual void on_setting_changed(const QString &key, const QVariant &value); /** * Paints the signal label. * @param p the QPainter to paint into. * @param rect the rectangle of the header area. * @param hover true if the label is being hovered over by the mouse. */ virtual void paint_label(QPainter &p, const QRect &rect, bool hover); virtual QMenu* create_header_context_menu(QWidget *parent); virtual QMenu* create_view_context_menu(QWidget *parent, QPoint &click_pos); pv::widgets::Popup* create_popup(QWidget *parent); /** * Computes the outline rectangle of a label. * @param rect the rectangle of the header area. * @return Returns the rectangle of the signal label. */ QRectF label_rect(const QRectF &rect) const; /** * Computes the outline rectangle of the viewport hit-box. * @param rect the rectangle of the viewport area. * @return Returns the rectangle of the hit-box. * @remarks The default implementation returns an empty hit-box. */ virtual QRectF hit_box_rect(const ViewItemPaintParams &pp) const; void set_current_segment(const int segment); int get_current_segment() const; virtual void hover_point_changed(const QPoint &hp); protected: /** * Paints the background layer of the signal with a QPainter. * @param p The QPainter to paint into. * @param pp The painting parameters object to paint with. */ virtual void paint_back(QPainter &p, ViewItemPaintParams &pp); /** * Paints a zero axis across the viewport. * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with. * @param y the y-offset of the axis. */ void paint_axis(QPainter &p, ViewItemPaintParams &pp, int y); /** * Draw a hover marker under the cursor position. * @param p The painter to draw into. */ void paint_hover_marker(QPainter &p); void add_color_option(QWidget *parent, QFormLayout *form); void create_popup_form(); virtual void populate_popup_form(QWidget *parent, QFormLayout *form); protected Q_SLOTS: virtual void on_name_changed(const QString &text); virtual void on_color_changed(const QColor &color); void on_popup_closed(); private Q_SLOTS: void on_nameedit_changed(const QString &name); void on_coloredit_changed(const QColor &color); void on_create_marker_here() const; protected: shared_ptr base_; QPen axis_pen_; SegmentDisplayMode segment_display_mode_; bool show_hover_marker_; uint32_t context_menu_x_pos_; /// The ID of the currently displayed segment int current_segment_; private: pv::widgets::Popup *popup_; QFormLayout *popup_form_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACE_HPP pulseview-0.4.1/pv/views/trace/standardbar.cpp000600 001750 001750 00000022403 13365561737 021063 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2016 Soeren Apel * Copyright (C) 2012-2015 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include "standardbar.hpp" #include "view.hpp" #include using pv::views::trace::View; namespace pv { namespace views { namespace trace { StandardBar::StandardBar(Session &session, QWidget *parent, View *view, bool add_default_widgets) : QToolBar("Standard Trace View Toolbar", parent), session_(session), view_(view), action_view_zoom_in_(new QAction(this)), action_view_zoom_out_(new QAction(this)), action_view_zoom_fit_(new QAction(this)), action_view_show_cursors_(new QAction(this)), segment_display_mode_selector_(new QToolButton(this)), action_sdm_last_(new QAction(this)), action_sdm_last_complete_(new QAction(this)), action_sdm_single_(new QAction(this)), segment_selector_(new QSpinBox(this)) { setObjectName(QString::fromUtf8("StandardBar")); // Actions action_view_zoom_in_->setText(tr("Zoom &In")); action_view_zoom_in_->setIcon(QIcon::fromTheme("zoom-in", QIcon(":/icons/zoom-in.png"))); // simply using Qt::Key_Plus shows no + in the menu action_view_zoom_in_->setShortcut(QKeySequence::ZoomIn); connect(action_view_zoom_in_, SIGNAL(triggered(bool)), this, SLOT(on_actionViewZoomIn_triggered())); action_view_zoom_out_->setText(tr("Zoom &Out")); action_view_zoom_out_->setIcon(QIcon::fromTheme("zoom-out", QIcon(":/icons/zoom-out.png"))); action_view_zoom_out_->setShortcut(QKeySequence::ZoomOut); connect(action_view_zoom_out_, SIGNAL(triggered(bool)), this, SLOT(on_actionViewZoomOut_triggered())); action_view_zoom_fit_->setCheckable(true); action_view_zoom_fit_->setText(tr("Zoom to &Fit")); action_view_zoom_fit_->setIcon(QIcon::fromTheme("zoom-fit-best", QIcon(":/icons/zoom-fit-best.png"))); action_view_zoom_fit_->setShortcut(QKeySequence(Qt::Key_F)); connect(action_view_zoom_fit_, SIGNAL(triggered(bool)), this, SLOT(on_actionViewZoomFit_triggered(bool))); action_view_show_cursors_->setCheckable(true); action_view_show_cursors_->setIcon(QIcon(":/icons/show-cursors.svg")); action_view_show_cursors_->setShortcut(QKeySequence(Qt::Key_C)); connect(action_view_show_cursors_, SIGNAL(triggered(bool)), this, SLOT(on_actionViewShowCursors_triggered())); action_view_show_cursors_->setText(tr("Show &Cursors")); action_sdm_last_->setIcon(QIcon(":/icons/view-displaymode-last_segment.svg")); action_sdm_last_->setText(tr("Display last segment only")); connect(action_sdm_last_, SIGNAL(triggered(bool)), this, SLOT(on_actionSDMLast_triggered())); action_sdm_last_complete_->setIcon(QIcon(":/icons/view-displaymode-last_complete_segment.svg")); action_sdm_last_complete_->setText(tr("Display last complete segment only")); connect(action_sdm_last_complete_, SIGNAL(triggered(bool)), this, SLOT(on_actionSDMLastComplete_triggered())); action_sdm_single_->setIcon(QIcon(":/icons/view-displaymode-single_segment.svg")); action_sdm_single_->setText(tr("Display a single segment")); connect(action_view_show_cursors_, SIGNAL(triggered(bool)), this, SLOT(on_actionSDMSingle_triggered())); segment_display_mode_selector_->addAction(action_sdm_last_); segment_display_mode_selector_->addAction(action_sdm_last_complete_); segment_display_mode_selector_->addAction(action_sdm_single_); segment_display_mode_selector_->setPopupMode(QToolButton::InstantPopup); segment_display_mode_selector_->hide(); segment_selector_->setMinimum(1); segment_selector_->hide(); connect(&session_, SIGNAL(new_segment(int)), this, SLOT(on_new_segment(int))); connect(&session_, SIGNAL(segment_completed(int)), view_, SLOT(on_segment_completed(int))); connect(segment_selector_, SIGNAL(valueChanged(int)), this, SLOT(on_segment_selected(int))); connect(view_, SIGNAL(segment_changed(int)), this, SLOT(on_segment_changed(int))); connect(this, SIGNAL(segment_selected(int)), view_, SLOT(on_segment_changed(int))); connect(view_, SIGNAL(segment_display_mode_changed(int, bool)), this, SLOT(on_segment_display_mode_changed(int, bool))); connect(view_, SIGNAL(always_zoom_to_fit_changed(bool)), this, SLOT(on_always_zoom_to_fit_changed(bool))); connect(view_, SIGNAL(cursor_state_changed(bool)), this, SLOT(on_cursor_state_changed(bool))); if (add_default_widgets) add_toolbar_widgets(); } Session &StandardBar::session() const { return session_; } void StandardBar::add_toolbar_widgets() { // Setup the toolbar addAction(action_view_zoom_in_); addAction(action_view_zoom_out_); addAction(action_view_zoom_fit_); addSeparator(); addAction(action_view_show_cursors_); multi_segment_actions_.push_back(addSeparator()); multi_segment_actions_.push_back(addWidget(segment_display_mode_selector_)); multi_segment_actions_.push_back(addWidget(segment_selector_)); addSeparator(); // Hide the multi-segment UI until we know that there are multiple segments show_multi_segment_ui(false); } void StandardBar::show_multi_segment_ui(const bool state) { for (QAction* action : multi_segment_actions_) action->setVisible(state); on_segment_display_mode_changed(view_->segment_display_mode(), view_->segment_is_selectable()); } QAction* StandardBar::action_view_zoom_in() const { return action_view_zoom_in_; } QAction* StandardBar::action_view_zoom_out() const { return action_view_zoom_out_; } QAction* StandardBar::action_view_zoom_fit() const { return action_view_zoom_fit_; } QAction* StandardBar::action_view_show_cursors() const { return action_view_show_cursors_; } void StandardBar::on_actionViewZoomIn_triggered() { view_->zoom(1); } void StandardBar::on_actionViewZoomOut_triggered() { view_->zoom(-1); } void StandardBar::on_actionViewZoomFit_triggered(bool checked) { view_->zoom_fit(checked); } void StandardBar::on_actionViewShowCursors_triggered() { const bool show = action_view_show_cursors_->isChecked(); if (show) view_->centre_cursors(); view_->show_cursors(show); } void StandardBar::on_actionSDMLast_triggered() { view_->set_segment_display_mode(Trace::ShowLastSegmentOnly); } void StandardBar::on_actionSDMLastComplete_triggered() { view_->set_segment_display_mode(Trace::ShowLastCompleteSegmentOnly); } void StandardBar::on_actionSDMSingle_triggered() { view_->set_segment_display_mode(Trace::ShowSingleSegmentOnly); } void StandardBar::on_always_zoom_to_fit_changed(bool state) { action_view_zoom_fit_->setChecked(state); } void StandardBar::on_new_segment(int new_segment_id) { if (new_segment_id > 0) { show_multi_segment_ui(true); segment_selector_->setMaximum(new_segment_id + 1); } else show_multi_segment_ui(false); } void StandardBar::on_segment_changed(int segment_id) { // We need to adjust the value by 1 because internally, segments // start at 0 while they start with 1 for the spinbox const uint32_t ui_segment_id = segment_id + 1; // This is called when the current segment was changed // by other parts of the UI, e.g. the view itself // Make sure our value isn't limited by a too low maximum // Note: this can happen if on_segment_changed() is called before // on_new_segment() if ((uint32_t)segment_selector_->maximum() < ui_segment_id) segment_selector_->setMaximum(ui_segment_id); segment_selector_->setValue(ui_segment_id); } void StandardBar::on_segment_selected(int ui_segment_id) { // We need to adjust the value by 1 because internally, segments // start at 0 while they start with 1 for the spinbox const uint32_t segment_id = ui_segment_id - 1; // This is called when the user selected a segment using the spin box // or when the value of the spinbox was assigned a new value. Since we // only care about the former, we filter out the latter: if (segment_id == view_->current_segment()) return; // No matter which segment display mode we were in, we now show a single segment if (view_->segment_display_mode() != Trace::ShowSingleSegmentOnly) on_actionSDMSingle_triggered(); segment_selected(segment_id); } void StandardBar::on_segment_display_mode_changed(int mode, bool segment_selectable) { segment_selector_->setReadOnly(!segment_selectable); switch ((Trace::SegmentDisplayMode)mode) { case Trace::ShowLastSegmentOnly: segment_display_mode_selector_->setDefaultAction(action_sdm_last_); break; case Trace::ShowLastCompleteSegmentOnly: segment_display_mode_selector_->setDefaultAction(action_sdm_last_complete_); break; case Trace::ShowSingleSegmentOnly: segment_display_mode_selector_->setDefaultAction(action_sdm_single_); break; default: break; } } void StandardBar::on_cursor_state_changed(bool show) { action_view_show_cursors_->setChecked(show); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/rowitem.cpp000600 001750 001750 00000001717 13365561737 020271 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2015 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "rowitem.hpp" namespace pv { namespace views { namespace trace { void RowItem::hover_point_changed(const QPoint &hp) { (void)hp; } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/tracepalette.hpp000600 001750 001750 00000002301 13365561737 021253 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEPALETTE_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEPALETTE_HPP #include namespace pv { namespace views { namespace trace { class TracePalette { public: static const unsigned int Cols = 8; static const unsigned int Rows = 4; static const QColor Colors[Cols * Rows]; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEPALETTE_HPP pulseview-0.4.1/pv/views/trace/viewitemowner.cpp000600 001750 001750 00000002645 13365561737 021510 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include "tracetreeitem.hpp" #include "trace.hpp" #include "tracetreeitemowner.hpp" namespace pv { namespace views { namespace trace { const ViewItemOwner::item_list& ViewItemOwner::child_items() const { return items_; } ViewItemOwner::iterator ViewItemOwner::begin() { return iterator(this, items_.begin()); } ViewItemOwner::iterator ViewItemOwner::end() { return iterator(this); } ViewItemOwner::const_iterator ViewItemOwner::begin() const { return const_iterator(this, items_.cbegin()); } ViewItemOwner::const_iterator ViewItemOwner::end() const { return const_iterator(this); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/header.hpp000600 001750 001750 00000004603 13365561737 020035 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_HEADER_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_HEADER_HPP #include #include #include #include "marginwidget.hpp" using std::shared_ptr; using std::vector; namespace pv { namespace views { namespace trace { class TraceTreeItem; class View; class ViewItem; /** * The Header class provides an area for @ref Trace labels to be shown, * trace-related settings to be edited, trace groups to be shown and similar. * Essentially, it is the main management area of the @ref View itself and * shown on the left-hand side of the trace area. */ class Header : public MarginWidget { Q_OBJECT private: static const int Padding; public: Header(View &parent); QSize sizeHint() const; /** * The extended area that the header widget would like to be sized to. * @remarks This area is the area specified by sizeHint, extended by * the area to overlap the viewport. */ QSize extended_size_hint() const; private: /** * Gets the row items. */ vector< shared_ptr > items(); /** * Gets the first view item which has a label that contains @c pt . * @param pt the point to search with. * @return the view item that has been found, or and empty * @c shared_ptr if no item was found. */ shared_ptr get_mouse_over_item(const QPoint &pt); private: void paintEvent(QPaintEvent *event); private: void contextMenuEvent(QContextMenuEvent *event); void keyPressEvent(QKeyEvent *event); private Q_SLOTS: void on_group(); void on_ungroup(); }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_HEADER_HPP pulseview-0.4.1/pv/views/trace/tracetreeitemowner.cpp000600 001750 001750 00000007306 13365561737 022513 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include "tracetreeitem.hpp" #include "trace.hpp" #include "tracetreeitemowner.hpp" using std::find; using std::make_pair; using std::max; using std::min; using std::pair; using std::shared_ptr; using std::static_pointer_cast; using std::vector; namespace pv { namespace views { namespace trace { const ViewItemOwner::item_list& TraceTreeItemOwner::child_items() const { return items_; } vector< shared_ptr > TraceTreeItemOwner::trace_tree_child_items() const { vector< shared_ptr > items; for (auto &i : items_) { assert(dynamic_pointer_cast(i)); const shared_ptr t( static_pointer_cast(i)); items.push_back(t); } return items; } void TraceTreeItemOwner::clear_child_items() { for (auto &t : trace_tree_child_items()) { assert(t->owner() == this); t->set_owner(nullptr); } items_.clear(); } void TraceTreeItemOwner::add_child_item(shared_ptr item) { assert(!item->owner()); item->set_owner(this); items_.push_back(item); extents_changed(true, true); } void TraceTreeItemOwner::remove_child_item(shared_ptr item) { assert(item->owner() == this); item->set_owner(nullptr); auto iter = find(items_.begin(), items_.end(), item); assert(iter != items_.end()); items_.erase(iter); extents_changed(true, true); } pair TraceTreeItemOwner::v_extents() const { bool has_children = false; pair extents(INT_MAX, INT_MIN); for (const shared_ptr& t : trace_tree_child_items()) { assert(t); if (!t->enabled()) continue; has_children = true; const int child_offset = t->layout_v_offset(); const pair child_extents = t->v_extents(); extents.first = min(child_extents.first + child_offset, extents.first); extents.second = max(child_extents.second + child_offset, extents.second); } if (!has_children) extents = make_pair(0, 0); return extents; } void TraceTreeItemOwner::restack_items() { vector> items(trace_tree_child_items()); // Sort by the centre line of the extents stable_sort(items.begin(), items.end(), [](const shared_ptr &a, const shared_ptr &b) { const auto aext = a->v_extents(); const auto bext = b->v_extents(); return a->layout_v_offset() + (aext.first + aext.second) / 2 < b->layout_v_offset() + (bext.first + bext.second) / 2; }); int total_offset = 0; for (shared_ptr r : items) { const pair extents = r->v_extents(); if (extents.first == 0 && extents.second == 0) continue; // We position disabled traces, so that they are close to the // animation target positon should they be re-enabled if (r->enabled()) total_offset += -extents.first; if (!r->dragging()) r->set_layout_v_offset(total_offset); if (r->enabled()) total_offset += extents.second; } } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/decodetrace.cpp000600 001750 001750 00000107375 13365561737 021054 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "decodetrace.hpp" #include "view.hpp" #include "viewport.hpp" #include #include #include #include #include #include #include #include #include #include using std::abs; using std::make_pair; using std::max; using std::min; using std::numeric_limits; using std::out_of_range; using std::pair; using std::shared_ptr; using std::tie; using std::vector; using pv::data::decode::Annotation; using pv::data::decode::Row; using pv::data::DecodeChannel; using pv::data::DecodeSignal; namespace pv { namespace views { namespace trace { #define DECODETRACE_COLOR_SATURATION (180) /* 0-255 */ #define DECODETRACE_COLOR_VALUE (170) /* 0-255 */ const QColor DecodeTrace::ErrorBgColor = QColor(0xEF, 0x29, 0x29); const QColor DecodeTrace::NoDecodeColor = QColor(0x88, 0x8A, 0x85); const int DecodeTrace::ArrowSize = 4; const double DecodeTrace::EndCapWidth = 5; const int DecodeTrace::RowTitleMargin = 10; const int DecodeTrace::DrawPadding = 100; const int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz DecodeTrace::DecodeTrace(pv::Session &session, shared_ptr signalbase, int index) : Trace(signalbase), session_(session), row_height_(0), max_visible_rows_(0), delete_mapper_(this), show_hide_mapper_(this) { decode_signal_ = dynamic_pointer_cast(base_); // Determine shortest string we want to see displayed in full QFontMetrics m(QApplication::font()); min_useful_label_width_ = m.width("XX"); // e.g. two hex characters // For the base color, we want to start at a very different color for // every decoder stack, so multiply the index with a number that is // rather close to 180 degrees of the color circle but not a dividend of 360 // Note: The offset equals the color of the first annotation QColor color; const int h = (120 + 160 * index) % 360; const int s = DECODETRACE_COLOR_SATURATION; const int v = DECODETRACE_COLOR_VALUE; color.setHsv(h, s, v); base_->set_color(color); connect(decode_signal_.get(), SIGNAL(new_annotations()), this, SLOT(on_new_annotations())); connect(decode_signal_.get(), SIGNAL(decode_reset()), this, SLOT(on_decode_reset())); connect(decode_signal_.get(), SIGNAL(decode_finished()), this, SLOT(on_decode_finished())); connect(decode_signal_.get(), SIGNAL(channels_updated()), this, SLOT(on_channels_updated())); connect(&delete_mapper_, SIGNAL(mapped(int)), this, SLOT(on_delete_decoder(int))); connect(&show_hide_mapper_, SIGNAL(mapped(int)), this, SLOT(on_show_hide_decoder(int))); connect(&delayed_trace_updater_, SIGNAL(timeout()), this, SLOT(on_delayed_trace_update())); delayed_trace_updater_.setSingleShot(true); delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate); } bool DecodeTrace::enabled() const { return true; } shared_ptr DecodeTrace::base() const { return base_; } pair DecodeTrace::v_extents() const { const int row_height = (ViewItemPaintParams::text_height() * 6) / 4; // Make an empty decode trace appear symmetrical const int row_count = max(1, max_visible_rows_); return make_pair(-row_height, row_height * row_count); } void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp) { Trace::paint_back(p, pp); paint_axis(p, pp, get_visual_y()); } void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp) { const int text_height = ViewItemPaintParams::text_height(); row_height_ = (text_height * 6) / 4; const int annotation_height = (text_height * 5) / 4; // Set default pen to allow for text width calculation p.setPen(Qt::black); // Iterate through the rows int y = get_visual_y(); pair sample_range = get_view_sample_range(pp.left(), pp.right()); // Just because the view says we see a certain sample range it // doesn't mean we have this many decoded samples, too, so crop // the range to what has been decoded already sample_range.second = min((int64_t)sample_range.second, decode_signal_->get_decoded_sample_count(current_segment_, false)); const vector rows = decode_signal_->visible_rows(); visible_rows_.clear(); for (const Row& row : rows) { // Cache the row title widths int row_title_width; try { row_title_width = row_title_widths_.at(row); } catch (out_of_range&) { const int w = p.boundingRect(QRectF(), 0, row.title()).width() + RowTitleMargin; row_title_widths_[row] = w; row_title_width = w; } vector annotations; decode_signal_->get_annotation_subset(annotations, row, current_segment_, sample_range.first, sample_range.second); if (!annotations.empty()) { draw_annotations(annotations, p, annotation_height, pp, y, get_row_color(row.index()), row_title_width); y += row_height_; visible_rows_.push_back(row); } } draw_unresolved_period(p, annotation_height, pp.left(), pp.right()); if ((int)visible_rows_.size() > max_visible_rows_) { max_visible_rows_ = (int)visible_rows_.size(); // Call order is important, otherwise the lazy event handler won't work owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } const QString err = decode_signal_->error_message(); if (!err.isEmpty()) draw_error(p, err, pp); } void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp) { assert(row_height_); for (size_t i = 0; i < visible_rows_.size(); i++) { const int y = i * row_height_ + get_visual_y(); p.setPen(QPen(Qt::NoPen)); p.setBrush(QApplication::palette().brush(QPalette::WindowText)); if (i != 0) { const QPointF points[] = { QPointF(pp.left(), y - ArrowSize), QPointF(pp.left() + ArrowSize, y), QPointF(pp.left(), y + ArrowSize) }; p.drawPolygon(points, countof(points)); } const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2, pp.right() - pp.left(), row_height_); const QString h(visible_rows_[i].title()); const int f = Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip; // Draw the outline p.setPen(QApplication::palette().color(QPalette::Base)); for (int dx = -1; dx <= 1; dx++) for (int dy = -1; dy <= 1; dy++) if (dx != 0 && dy != 0) p.drawText(r.translated(dx, dy), f, h); // Draw the text p.setPen(QApplication::palette().color(QPalette::WindowText)); p.drawText(r, f, h); } if (show_hover_marker_) paint_hover_marker(p); } void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form) { using pv::data::decode::Decoder; assert(form); // Add the standard options Trace::populate_popup_form(parent, form); // Add the decoder options bindings_.clear(); channel_id_map_.clear(); init_state_map_.clear(); decoder_forms_.clear(); const vector< shared_ptr > &stack = decode_signal_->decoder_stack(); if (stack.empty()) { QLabel *const l = new QLabel( tr("

No decoders in the stack

")); l->setAlignment(Qt::AlignCenter); form->addRow(l); } else { auto iter = stack.cbegin(); for (int i = 0; i < (int)stack.size(); i++, iter++) { shared_ptr dec(*iter); create_decoder_form(i, dec, parent, form); } form->addRow(new QLabel( tr("* Required channels"), parent)); } // Add stacking button pv::widgets::DecoderMenu *const decoder_menu = new pv::widgets::DecoderMenu(parent); connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)), this, SLOT(on_stack_decoder(srd_decoder*))); QPushButton *const stack_button = new QPushButton(tr("Stack Decoder"), parent); stack_button->setMenu(decoder_menu); stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one")); QHBoxLayout *stack_button_box = new QHBoxLayout; stack_button_box->addWidget(stack_button, 0, Qt::AlignRight); form->addRow(stack_button_box); } QMenu* DecodeTrace::create_header_context_menu(QWidget *parent) { QMenu *const menu = Trace::create_header_context_menu(parent); menu->addSeparator(); QAction *const del = new QAction(tr("Delete"), this); del->setShortcuts(QKeySequence::Delete); connect(del, SIGNAL(triggered()), this, SLOT(on_delete())); menu->addAction(del); return menu; } QMenu* DecodeTrace::create_view_context_menu(QWidget *parent, QPoint &click_pos) { // Get entries from default menu before adding our own QMenu *const menu = new QMenu(parent); QMenu* default_menu = Trace::create_view_context_menu(parent, click_pos); if (default_menu) { for (QAction *action : default_menu->actions()) { // clazy:exclude=range-loop menu->addAction(action); if (action->parent() == default_menu) action->setParent(menu); } delete default_menu; // Add separator if needed if (menu->actions().length() > 0) menu->addSeparator(); } try { selected_row_ = &visible_rows_[get_row_at_point(click_pos)]; } catch (out_of_range&) { selected_row_ = nullptr; } // Default sample range is "from here" const pair sample_range = get_view_sample_range(click_pos.x(), click_pos.x() + 1); selected_sample_range_ = make_pair(sample_range.first, numeric_limits::max()); if (decode_signal_->is_paused()) { QAction *const resume = new QAction(tr("Resume decoding"), this); resume->setIcon(QIcon::fromTheme("media-playback-start", QIcon(":/icons/media-playback-start.png"))); connect(resume, SIGNAL(triggered()), this, SLOT(on_pause_decode())); menu->addAction(resume); } else { QAction *const pause = new QAction(tr("Pause decoding"), this); pause->setIcon(QIcon::fromTheme("media-playback-pause", QIcon(":/icons/media-playback-pause.png"))); connect(pause, SIGNAL(triggered()), this, SLOT(on_pause_decode())); menu->addAction(pause); } menu->addSeparator(); QAction *const export_all_rows = new QAction(tr("Export all annotations"), this); export_all_rows->setIcon(QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png"))); connect(export_all_rows, SIGNAL(triggered()), this, SLOT(on_export_all_rows())); menu->addAction(export_all_rows); QAction *const export_row = new QAction(tr("Export all annotations for this row"), this); export_row->setIcon(QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png"))); connect(export_row, SIGNAL(triggered()), this, SLOT(on_export_row())); menu->addAction(export_row); menu->addSeparator(); QAction *const export_all_rows_from_here = new QAction(tr("Export all annotations, starting here"), this); export_all_rows_from_here->setIcon(QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png"))); connect(export_all_rows_from_here, SIGNAL(triggered()), this, SLOT(on_export_all_rows_from_here())); menu->addAction(export_all_rows_from_here); QAction *const export_row_from_here = new QAction(tr("Export annotations for this row, starting here"), this); export_row_from_here->setIcon(QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png"))); connect(export_row_from_here, SIGNAL(triggered()), this, SLOT(on_export_row_from_here())); menu->addAction(export_row_from_here); menu->addSeparator(); QAction *const export_all_rows_with_cursor = new QAction(tr("Export all annotations within cursor range"), this); export_all_rows_with_cursor->setIcon(QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png"))); connect(export_all_rows_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_all_rows_with_cursor())); menu->addAction(export_all_rows_with_cursor); QAction *const export_row_with_cursor = new QAction(tr("Export annotations for this row within cursor range"), this); export_row_with_cursor->setIcon(QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png"))); connect(export_row_with_cursor, SIGNAL(triggered()), this, SLOT(on_export_row_with_cursor())); menu->addAction(export_row_with_cursor); const View *view = owner_->view(); assert(view); if (!view->cursors()->enabled()) { export_all_rows_with_cursor->setEnabled(false); export_row_with_cursor->setEnabled(false); } return menu; } void DecodeTrace::draw_annotations(vector annotations, QPainter &p, int h, const ViewItemPaintParams &pp, int y, QColor row_color, int row_title_width) { using namespace pv::data::decode; Annotation::Class block_class = 0; bool block_class_uniform = true; qreal block_start = 0; int block_ann_count = 0; const Annotation *prev_ann; qreal prev_end = INT_MIN; qreal a_end; double samples_per_pixel, pixels_offset; tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel(); // Sort the annotations by start sample so that decoders // can't confuse us by creating annotations out of order stable_sort(annotations.begin(), annotations.end(), [](const Annotation &a, const Annotation &b) { return a.start_sample() < b.start_sample(); }); // Gather all annotations that form a visual "block" and draw them as such for (const Annotation &a : annotations) { const qreal abs_a_start = a.start_sample() / samples_per_pixel; const qreal abs_a_end = a.end_sample() / samples_per_pixel; const qreal a_start = abs_a_start - pixels_offset; a_end = abs_a_end - pixels_offset; const qreal a_width = a_end - a_start; const qreal delta = a_end - prev_end; bool a_is_separate = false; // Annotation wider than the threshold for a useful label width? if (a_width >= min_useful_label_width_) { for (const QString &ann_text : a.annotations()) { const qreal w = p.boundingRect(QRectF(), 0, ann_text).width(); // Annotation wide enough to fit a label? Don't put it in a block then if (w <= a_width) { a_is_separate = true; break; } } } // Were the previous and this annotation more than a pixel apart? if ((abs(delta) > 1) || a_is_separate) { // Block was broken, draw annotations that form the current block if (block_ann_count == 1) draw_annotation(*prev_ann, p, h, pp, y, row_color, row_title_width); else if (block_ann_count > 0) draw_annotation_block(block_start, prev_end, block_class, block_class_uniform, p, h, y, row_color); block_ann_count = 0; } if (a_is_separate) { draw_annotation(a, p, h, pp, y, row_color, row_title_width); // Next annotation must start a new block. delta will be > 1 // because we set prev_end to INT_MIN but that's okay since // block_ann_count will be 0 and nothing will be drawn prev_end = INT_MIN; block_ann_count = 0; } else { prev_end = a_end; prev_ann = &a; if (block_ann_count == 0) { block_start = a_start; block_class = a.ann_class(); block_class_uniform = true; } else if (a.ann_class() != block_class) block_class_uniform = false; block_ann_count++; } } if (block_ann_count == 1) draw_annotation(*prev_ann, p, h, pp, y, row_color, row_title_width); else if (block_ann_count > 0) draw_annotation_block(block_start, prev_end, block_class, block_class_uniform, p, h, y, row_color); } void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a, QPainter &p, int h, const ViewItemPaintParams &pp, int y, QColor row_color, int row_title_width) const { double samples_per_pixel, pixels_offset; tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel(); const double start = a.start_sample() / samples_per_pixel - pixels_offset; const double end = a.end_sample() / samples_per_pixel - pixels_offset; QColor color = get_annotation_color(row_color, a.ann_class()); p.setPen(color.darker()); p.setBrush(color); if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding) return; if (a.start_sample() == a.end_sample()) draw_instant(a, p, h, start, y); else draw_range(a, p, h, start, end, y, pp, row_title_width); } void DecodeTrace::draw_annotation_block(qreal start, qreal end, Annotation::Class ann_class, bool use_ann_format, QPainter &p, int h, int y, QColor row_color) const { const double top = y + .5 - h / 2; const double bottom = y + .5 + h / 2; const QRectF rect(start, top, end - start, bottom - top); const int r = h / 4; p.setPen(QPen(Qt::NoPen)); p.setBrush(Qt::white); p.drawRoundedRect(rect, r, r); // If all annotations in this block are of the same type, we can use the // one format that all of these annotations have. Otherwise, we should use // a neutral color (i.e. gray) if (use_ann_format) { const QColor color = get_annotation_color(row_color, ann_class); p.setPen(color.darker()); p.setBrush(QBrush(color, Qt::Dense4Pattern)); } else { p.setPen(Qt::gray); p.setBrush(QBrush(Qt::gray, Qt::Dense4Pattern)); } p.drawRoundedRect(rect, r, r); } void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p, int h, qreal x, int y) const { const QString text = a.annotations().empty() ? QString() : a.annotations().back(); const qreal w = min((qreal)p.boundingRect(QRectF(), 0, text).width(), 0.0) + h; const QRectF rect(x - w / 2, y - h / 2, w, h); p.drawRoundedRect(rect, h / 2, h / 2); p.setPen(Qt::black); p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text); } void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p, int h, qreal start, qreal end, int y, const ViewItemPaintParams &pp, int row_title_width) const { const qreal top = y + .5 - h / 2; const qreal bottom = y + .5 + h / 2; const vector annotations = a.annotations(); // If the two ends are within 1 pixel, draw a vertical line if (start + 1.0 > end) { p.drawLine(QPointF(start, top), QPointF(start, bottom)); return; } const qreal cap_width = min((end - start) / 4, EndCapWidth); QPointF pts[] = { QPointF(start, y + .5f), QPointF(start + cap_width, top), QPointF(end - cap_width, top), QPointF(end, y + .5f), QPointF(end - cap_width, bottom), QPointF(start + cap_width, bottom) }; p.drawConvexPolygon(pts, countof(pts)); if (annotations.empty()) return; const int ann_start = start + cap_width; const int ann_end = end - cap_width; const int real_start = max(ann_start, pp.left() + row_title_width); const int real_end = min(ann_end, pp.right()); const int real_width = real_end - real_start; QRectF rect(real_start, y - h / 2, real_width, h); if (rect.width() <= 4) return; p.setPen(Qt::black); // Try to find an annotation that will fit QString best_annotation; int best_width = 0; for (const QString &a : annotations) { const int w = p.boundingRect(QRectF(), 0, a).width(); if (w <= rect.width() && w > best_width) best_annotation = a, best_width = w; } if (best_annotation.isEmpty()) best_annotation = annotations.back(); // If not ellide the last in the list p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText( best_annotation, Qt::ElideRight, rect.width())); } void DecodeTrace::draw_error(QPainter &p, const QString &message, const ViewItemPaintParams &pp) { const int y = get_visual_y(); double samples_per_pixel, pixels_offset; tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel(); p.setPen(ErrorBgColor.darker()); p.setBrush(ErrorBgColor); const QRectF bounding_rect = QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX); const QRectF text_rect = p.boundingRect(bounding_rect, Qt::AlignCenter, message); const qreal r = text_rect.height() / 4; p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r, Qt::AbsoluteSize); p.setPen(Qt::black); p.drawText(text_rect, message); } void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const { using namespace pv::data; using pv::data::decode::Decoder; double samples_per_pixel, pixels_offset; const int64_t sample_count = decode_signal_->get_working_sample_count(current_segment_); if (sample_count == 0) return; const int64_t samples_decoded = decode_signal_->get_decoded_sample_count(current_segment_, true); if (sample_count == samples_decoded) return; const int y = get_visual_y(); tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel(); const double start = max(samples_decoded / samples_per_pixel - pixels_offset, left - 1.0); const double end = min(sample_count / samples_per_pixel - pixels_offset, right + 1.0); const QRectF no_decode_rect(start, y - (h / 2) - 0.5, end - start, h); p.setPen(QPen(Qt::NoPen)); p.setBrush(Qt::white); p.drawRect(no_decode_rect); p.setPen(NoDecodeColor); p.setBrush(QBrush(NoDecodeColor, Qt::Dense6Pattern)); p.drawRect(no_decode_rect); } pair DecodeTrace::get_pixels_offset_samples_per_pixel() const { assert(owner_); const View *view = owner_->view(); assert(view); const double scale = view->scale(); assert(scale > 0); const double pixels_offset = ((view->offset() - decode_signal_->start_time()) / scale).convert_to(); double samplerate = decode_signal_->samplerate(); // Show sample rate as 1Hz when it is unknown if (samplerate == 0.0) samplerate = 1.0; return make_pair(pixels_offset, samplerate * scale); } pair DecodeTrace::get_view_sample_range( int x_start, int x_end) const { double samples_per_pixel, pixels_offset; tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel(); const uint64_t start = (uint64_t)max( (x_start + pixels_offset) * samples_per_pixel, 0.0); const uint64_t end = (uint64_t)max( (x_end + pixels_offset) * samples_per_pixel, 0.0); return make_pair(start, end); } QColor DecodeTrace::get_row_color(int row_index) const { // For each row color, use the base color hue and add an offset that's // not a dividend of 360 QColor color; const int h = (base_->color().toHsv().hue() + 20 * row_index) % 360; const int s = DECODETRACE_COLOR_SATURATION; const int v = DECODETRACE_COLOR_VALUE; color.setHsl(h, s, v); return color; } QColor DecodeTrace::get_annotation_color(QColor row_color, int annotation_index) const { // For each row color, use the base color hue and add an offset that's // not a dividend of 360 and not a multiple of the row offset QColor color(row_color); const int h = (color.toHsv().hue() + 55 * annotation_index) % 360; const int s = DECODETRACE_COLOR_SATURATION; const int v = DECODETRACE_COLOR_VALUE; color.setHsl(h, s, v); return color; } int DecodeTrace::get_row_at_point(const QPoint &point) { if (!row_height_) return -1; const int y = (point.y() - get_visual_y() + row_height_ / 2); /* Integer divison of (x-1)/x would yield 0, so we check for this. */ if (y < 0) return -1; const int row = y / row_height_; if (row >= (int)visible_rows_.size()) return -1; return row; } const QString DecodeTrace::get_annotation_at_point(const QPoint &point) { using namespace pv::data::decode; if (!enabled()) return QString(); const pair sample_range = get_view_sample_range(point.x(), point.x() + 1); const int row = get_row_at_point(point); if (row < 0) return QString(); vector annotations; decode_signal_->get_annotation_subset(annotations, visible_rows_[row], current_segment_, sample_range.first, sample_range.second); return (annotations.empty()) ? QString() : annotations[0].annotations().front(); } void DecodeTrace::hover_point_changed(const QPoint &hp) { Trace::hover_point_changed(hp); assert(owner_); const View *const view = owner_->view(); assert(view); if (hp.x() == 0) { QToolTip::hideText(); return; } QString ann = get_annotation_at_point(hp); assert(view); if (!row_height_ || ann.isEmpty()) { QToolTip::hideText(); return; } const int hover_row = get_row_at_point(hp); QFontMetrics m(QToolTip::font()); const QRect text_size = m.boundingRect(QRect(), 0, ann); // This is OS-specific and unfortunately we can't query it, so // use an approximation to at least try to minimize the error. const int padding = 8; // Make sure the tool tip doesn't overlap with the mouse cursor. // If it did, the tool tip would constantly hide and re-appear. // We also push it up by one row so that it appears above the // decode trace, not below. QPoint p = hp; p.setX(hp.x() - (text_size.width() / 2) - padding); p.setY(get_visual_y() - (row_height_ / 2) + (hover_row * row_height_) - row_height_ - text_size.height() - padding); QToolTip::showText(view->viewport()->mapToGlobal(p), ann); } void DecodeTrace::create_decoder_form(int index, shared_ptr &dec, QWidget *parent, QFormLayout *form) { GlobalSettings settings; assert(dec); const srd_decoder *const decoder = dec->decoder(); assert(decoder); const bool decoder_deletable = index > 0; pv::widgets::DecoderGroupBox *const group = new pv::widgets::DecoderGroupBox( QString::fromUtf8(decoder->name), tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname), QString::fromUtf8(decoder->desc)), nullptr, decoder_deletable); group->set_decoder_visible(dec->shown()); if (decoder_deletable) { delete_mapper_.setMapping(group, index); connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map())); } show_hide_mapper_.setMapping(group, index); connect(group, SIGNAL(show_hide_decoder()), &show_hide_mapper_, SLOT(map())); QFormLayout *const decoder_form = new QFormLayout; group->add_layout(decoder_form); const vector channels = decode_signal_->get_channels(); // Add the channels for (const DecodeChannel& ch : channels) { // Ignore channels not part of the decoder we create the form for if (ch.decoder_ != dec) continue; QComboBox *const combo = create_channel_selector(parent, &ch); QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch); channel_id_map_[combo] = ch.id; init_state_map_[combo_init_state] = ch.id; connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(on_channel_selected(int))); connect(combo_init_state, SIGNAL(currentIndexChanged(int)), this, SLOT(on_init_state_changed(int))); QHBoxLayout *const hlayout = new QHBoxLayout; hlayout->addWidget(combo); hlayout->addWidget(combo_init_state); if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool()) combo_init_state->hide(); const QString required_flag = ch.is_optional ? QString() : QString("*"); decoder_form->addRow(tr("%1 (%2) %3") .arg(ch.name, ch.desc, required_flag), hlayout); } // Add the options shared_ptr binding( new binding::Decoder(decode_signal_, dec)); binding->add_properties_to_form(decoder_form, true); bindings_.push_back(binding); form->addRow(group); decoder_forms_.push_back(group); } QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch) { const auto sigs(session_.signalbases()); // Sort signals in natural order vector< shared_ptr > sig_list(sigs.begin(), sigs.end()); sort(sig_list.begin(), sig_list.end(), [](const shared_ptr &a, const shared_ptr &b) { return strnatcasecmp(a->name().toStdString(), b->name().toStdString()) < 0; }); QComboBox *selector = new QComboBox(parent); selector->addItem("-", qVariantFromValue((void*)nullptr)); if (!ch->assigned_signal) selector->setCurrentIndex(0); for (const shared_ptr &b : sig_list) { assert(b); if (b->logic_data() && b->enabled()) { selector->addItem(b->name(), qVariantFromValue((void*)b.get())); if (ch->assigned_signal == b.get()) selector->setCurrentIndex(selector->count() - 1); } } return selector; } QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent, const DecodeChannel *ch) { QComboBox *selector = new QComboBox(parent); selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW)); selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH)); selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0)); selector->setCurrentIndex(ch->initial_pin_state); selector->setToolTip("Initial (assumed) pin value before the first sample"); return selector; } void DecodeTrace::export_annotations(vector *annotations) const { using namespace pv::data::decode; GlobalSettings settings; const QString dir = settings.value("MainWindow/SaveDirectory").toString(); const QString file_name = QFileDialog::getSaveFileName( owner_->view(), tr("Export annotations"), dir, tr("Text Files (*.txt);;All Files (*)")); if (file_name.isEmpty()) return; QString format = settings.value(GlobalSettings::Key_Dec_ExportFormat).toString(); const QString quote = format.contains("%q") ? "\"" : ""; format = format.remove("%q"); QFile file(file_name); if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { QTextStream out_stream(&file); for (Annotation &ann : *annotations) { const QString sample_range = QString("%1-%2") \ .arg(QString::number(ann.start_sample()), QString::number(ann.end_sample())); const QString class_name = quote + ann.row()->class_name() + quote; QString all_ann_text; for (const QString &s : ann.annotations()) all_ann_text = all_ann_text + quote + s + quote + ","; all_ann_text.chop(1); const QString first_ann_text = quote + ann.annotations().front() + quote; QString out_text = format; out_text = out_text.replace("%s", sample_range); out_text = out_text.replace("%d", quote + QString::fromUtf8(ann.row()->decoder()->name) + quote); out_text = out_text.replace("%c", class_name); out_text = out_text.replace("%1", first_ann_text); out_text = out_text.replace("%a", all_ann_text); out_stream << out_text << '\n'; } if (out_stream.status() == QTextStream::Ok) return; } QMessageBox msg(owner_->view()); msg.setText(tr("Error")); msg.setInformativeText(tr("File %1 could not be written to.").arg(file_name)); msg.setStandardButtons(QMessageBox::Ok); msg.setIcon(QMessageBox::Warning); msg.exec(); } void DecodeTrace::on_new_annotations() { if (!delayed_trace_updater_.isActive()) delayed_trace_updater_.start(); } void DecodeTrace::on_delayed_trace_update() { if (owner_) owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_decode_reset() { visible_rows_.clear(); max_visible_rows_ = 0; if (owner_) owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_decode_finished() { if (owner_) owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_pause_decode() { if (decode_signal_->is_paused()) decode_signal_->resume_decode(); else decode_signal_->pause_decode(); } void DecodeTrace::delete_pressed() { on_delete(); } void DecodeTrace::on_delete() { session_.remove_decode_signal(decode_signal_); } void DecodeTrace::on_channel_selected(int) { QComboBox *cb = qobject_cast(QObject::sender()); // Determine signal that was selected const data::SignalBase *signal = (data::SignalBase*)cb->itemData(cb->currentIndex()).value(); // Determine decode channel ID this combo box is the channel selector for const uint16_t id = channel_id_map_.at(cb); decode_signal_->assign_signal(id, signal); } void DecodeTrace::on_channels_updated() { if (owner_) owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_init_state_changed(int) { QComboBox *cb = qobject_cast(QObject::sender()); // Determine inital pin state that was selected int init_state = cb->itemData(cb->currentIndex()).value(); // Determine decode channel ID this combo box is the channel selector for const uint16_t id = init_state_map_.at(cb); decode_signal_->set_initial_pin_state(id, init_state); } void DecodeTrace::on_stack_decoder(srd_decoder *decoder) { decode_signal_->stack_decoder(decoder); create_popup_form(); } void DecodeTrace::on_delete_decoder(int index) { decode_signal_->remove_decoder(index); // Force re-calculation of the trace height, see paint_mid() max_visible_rows_ = 0; owner_->extents_changed(false, true); // Update the popup create_popup_form(); } void DecodeTrace::on_show_hide_decoder(int index) { const bool state = decode_signal_->toggle_decoder_visibility(index); assert(index < (int)decoder_forms_.size()); decoder_forms_[index]->set_decoder_visible(state); if (!state) { // Force re-calculation of the trace height, see paint_mid() max_visible_rows_ = 0; owner_->extents_changed(false, true); } if (owner_) owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_export_row() { selected_sample_range_ = make_pair(0, numeric_limits::max()); on_export_row_from_here(); } void DecodeTrace::on_export_all_rows() { selected_sample_range_ = make_pair(0, numeric_limits::max()); on_export_all_rows_from_here(); } void DecodeTrace::on_export_row_with_cursor() { const View *view = owner_->view(); assert(view); if (!view->cursors()->enabled()) return; const double samplerate = session_.get_samplerate(); const pv::util::Timestamp& start_time = view->cursors()->first()->time(); const pv::util::Timestamp& end_time = view->cursors()->second()->time(); const uint64_t start_sample = (uint64_t)max( 0.0, start_time.convert_to() * samplerate); const uint64_t end_sample = (uint64_t)max( 0.0, end_time.convert_to() * samplerate); // Are both cursors negative and thus were clamped to 0? if ((start_sample == 0) && (end_sample == 0)) return; selected_sample_range_ = make_pair(start_sample, end_sample); on_export_row_from_here(); } void DecodeTrace::on_export_all_rows_with_cursor() { const View *view = owner_->view(); assert(view); if (!view->cursors()->enabled()) return; const double samplerate = session_.get_samplerate(); const pv::util::Timestamp& start_time = view->cursors()->first()->time(); const pv::util::Timestamp& end_time = view->cursors()->second()->time(); const uint64_t start_sample = (uint64_t)max( 0.0, start_time.convert_to() * samplerate); const uint64_t end_sample = (uint64_t)max( 0.0, end_time.convert_to() * samplerate); // Are both cursors negative and thus were clamped to 0? if ((start_sample == 0) && (end_sample == 0)) return; selected_sample_range_ = make_pair(start_sample, end_sample); on_export_all_rows_from_here(); } void DecodeTrace::on_export_row_from_here() { using namespace pv::data::decode; if (!selected_row_) return; vector *annotations = new vector(); decode_signal_->get_annotation_subset(*annotations, *selected_row_, current_segment_, selected_sample_range_.first, selected_sample_range_.second); if (annotations->empty()) return; export_annotations(annotations); delete annotations; } void DecodeTrace::on_export_all_rows_from_here() { using namespace pv::data::decode; vector *annotations = new vector(); decode_signal_->get_annotation_subset(*annotations, current_segment_, selected_sample_range_.first, selected_sample_range_.second); if (!annotations->empty()) export_annotations(annotations); delete annotations; } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/logicsignal.hpp000600 001750 001750 00000010652 13365561737 021101 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP #include #include #include #include #include "signal.hpp" #include using std::pair; using std::shared_ptr; using std::vector; class QIcon; class QToolBar; namespace sigrok { class TriggerMatchType; } namespace pv { namespace devices { class Device; } namespace data { class Logic; } namespace views { namespace trace { class LogicSignal : public Signal { Q_OBJECT public: static const float Oversampling; static const QColor EdgeColor; static const QColor HighColor; static const QColor LowColor; static const QColor SamplingPointColor; static const QColor SignalColors[10]; static QColor TriggerMarkerBackgroundColor; static const int TriggerMarkerPadding; static const char* TriggerMarkerIcons[8]; LogicSignal(pv::Session &session, shared_ptr device, shared_ptr base); virtual ~LogicSignal() = default; shared_ptr data() const; shared_ptr logic_data() const; virtual void save_settings(QSettings &settings) const; virtual void restore_settings(QSettings &settings); /** * Computes the vertical extents of the contents of this row item. * @return A pair containing the minimum and maximum y-values. */ pair v_extents() const; /** * Paints the mid-layer of the signal with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with.. */ void paint_mid(QPainter &p, ViewItemPaintParams &pp); /** * Paints the foreground layer of the signal with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with. */ virtual void paint_fore(QPainter &p, ViewItemPaintParams &pp); /** * 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, vector< pair > &edges, bool level, double samples_per_pixel, double pixels_offset, float x_offset, float y_offset); shared_ptr get_logic_segment_to_paint() const; void init_trigger_actions(QWidget *parent); const vector get_trigger_types() const; QAction* action_from_trigger_type(const sigrok::TriggerMatchType *type); const sigrok::TriggerMatchType* trigger_type_from_action( QAction *action); void populate_popup_form(QWidget *parent, QFormLayout *form); void modify_trigger(); static const QIcon* get_icon(const char *path); static const QPixmap* get_pixmap(const char *path); private Q_SLOTS: void on_setting_changed(const QString &key, const QVariant &value); void on_trigger(); void on_signal_height_changed(int height); private: int signal_height_; QColor high_fill_color_; bool show_sampling_points_, fill_high_areas_; shared_ptr device_; QSpinBox *signal_height_sb_; const sigrok::TriggerMatchType *trigger_match_; const vector trigger_types_; QToolBar *trigger_bar_; QAction *trigger_none_; QAction *trigger_rising_; QAction *trigger_high_; QAction *trigger_falling_; QAction *trigger_low_; QAction *trigger_change_; static QCache icon_cache_; static QCache pixmap_cache_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_LOGICSIGNAL_HPP pulseview-0.4.1/pv/views/trace/viewwidget.hpp000600 001750 001750 00000007410 13365561737 020762 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWWIDGET_HPP #define PULSEVIEW_PV_VIEWWIDGET_HPP #include #include #include using std::shared_ptr; using std::vector; class QTouchEvent; namespace pv { namespace views { namespace trace { class View; class ViewItem; class ViewWidget : public QWidget { Q_OBJECT protected: ViewWidget(View &parent); /** * Indicates when a view item is being hovered over. * @param item The item that is being hovered over, or @c nullptr * if no view item is being hovered over. * @remarks the default implementation does nothing. */ virtual void item_hover(const shared_ptr &item, QPoint pos); /** * Indicates the event an a view item has been clicked. * @param item the view item that has been clicked. * @remarks the default implementation does nothing. */ virtual void item_clicked(const shared_ptr &item); /** * Returns true if the selection of row items allows dragging. * @return Returns true if the drag is acceptable. */ bool accept_drag() const; /** * Returns true if the mouse button is down. */ bool mouse_down() const; /** * Drag the dragging items by the delta offset. * @param delta the drag offset in pixels. */ void drag_items(const QPoint &delta); /** * Sets this item into the dragged state. */ virtual void drag(); /** * Drag the background by the delta offset. * @param delta the drag offset in pixels. * @remarks The default implementation does nothing. */ virtual void drag_by(const QPoint &delta); /** * Sets this item into the un-dragged state. */ virtual void drag_release(); /** * Gets the items in the view widget. */ virtual vector< shared_ptr > items() = 0; /** * Gets the first view item which has a hit-box that contains @c pt . * @param pt the point to search with. * @return the view item that has been found, or and empty * @c shared_ptr if no item was found. */ virtual shared_ptr get_mouse_over_item(const QPoint &pt) = 0; /** * Handles left mouse button press events. * @param event the mouse event that triggered this handler. */ void mouse_left_press_event(QMouseEvent *event); /** * Handles left mouse button release events. * @param event the mouse event that triggered this handler. */ void mouse_left_release_event(QMouseEvent *event); /** * Handles touch begin update and end events. * @param e the event that triggered this handler. */ virtual bool touch_event(QTouchEvent *event); protected: bool event(QEvent *event); void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void leaveEvent(QEvent *event); public Q_SLOTS: void clear_selection(); Q_SIGNALS: void selection_changed(); protected: pv::views::trace::View &view_; QPoint mouse_point_; QPoint mouse_down_point_; shared_ptr mouse_down_item_; bool item_dragging_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWWIDGET_HPP pulseview-0.4.1/pv/views/trace/tracetreeitem.cpp000600 001750 001750 00000006365 13365561737 021444 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include "view.hpp" #include "tracetreeitem.hpp" namespace pv { namespace views { namespace trace { TraceTreeItem::TraceTreeItem() : owner_(nullptr), layout_v_offset_(0), visual_v_offset_(0), v_offset_animation_(this, "visual_v_offset") { } void TraceTreeItem::select(bool select) { ViewItem::select(select); owner_->row_item_appearance_changed(true, true); } int TraceTreeItem::layout_v_offset() const { return layout_v_offset_; } void TraceTreeItem::set_layout_v_offset(int v_offset) { if (layout_v_offset_ == v_offset) return; layout_v_offset_ = v_offset; if (owner_) owner_->extents_changed(false, true); } int TraceTreeItem::visual_v_offset() const { return visual_v_offset_; } void TraceTreeItem::set_visual_v_offset(int v_offset) { visual_v_offset_ = v_offset; if (owner_) owner_->row_item_appearance_changed(true, true); } void TraceTreeItem::force_to_v_offset(int v_offset) { v_offset_animation_.stop(); layout_v_offset_ = visual_v_offset_ = v_offset; if (owner_) { owner_->row_item_appearance_changed(true, true); owner_->extents_changed(false, true); } } void TraceTreeItem::animate_to_layout_v_offset() { if (visual_v_offset_ == layout_v_offset_ || (v_offset_animation_.endValue() == layout_v_offset_ && v_offset_animation_.state() == QAbstractAnimation::Running)) return; v_offset_animation_.setDuration(100); v_offset_animation_.setStartValue(visual_v_offset_); v_offset_animation_.setEndValue(layout_v_offset_); v_offset_animation_.setEasingCurve(QEasingCurve::OutQuad); v_offset_animation_.start(); } TraceTreeItemOwner* TraceTreeItem::owner() const { return owner_; } void TraceTreeItem::set_owner(TraceTreeItemOwner *owner) { assert(owner_ || owner); v_offset_animation_.stop(); if (owner_) { const int owner_offset = owner_->owner_visual_v_offset(); layout_v_offset_ += owner_offset; visual_v_offset_ += owner_offset; } owner_ = owner; if (owner_) { const int owner_offset = owner_->owner_visual_v_offset(); layout_v_offset_ -= owner_offset; visual_v_offset_ -= owner_offset; } } int TraceTreeItem::get_visual_y() const { assert(owner_); return visual_v_offset_ + owner_->owner_visual_v_offset(); } void TraceTreeItem::drag_by(const QPoint &delta) { assert(owner_); force_to_v_offset(drag_point_.y() + delta.y() - owner_->owner_visual_v_offset()); } QPoint TraceTreeItem::drag_point(const QRect &rect) const { return QPoint(rect.right(), get_visual_y()); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/cursor.cpp000600 001750 001750 00000005230 13365561737 020112 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "cursor.hpp" #include "pv/util.hpp" #include "ruler.hpp" #include "view.hpp" #include #include #include #include #include #include #include #include #include using std::abs; // NOLINT. Force usage of std::abs() instead of C's abs(). using std::shared_ptr; namespace pv { namespace views { namespace trace { const QColor Cursor::FillColor(52, 101, 164); Cursor::Cursor(View &view, double time) : TimeMarker(view, FillColor, time) { } bool Cursor::enabled() const { return view_.cursors_shown(); } QString Cursor::get_text() const { const shared_ptr other = get_other_cursor(); const pv::util::Timestamp& diff = abs(time_ - other->time_); return Ruler::format_time_with_distance( diff, time_, view_.tick_prefix(), view_.time_unit(), view_.tick_precision()); } QRectF Cursor::label_rect(const QRectF &rect) const { const shared_ptr other(get_other_cursor()); assert(other); const float x = get_x(); QFontMetrics m(QApplication::font()); QSize text_size = m.boundingRect(get_text()).size(); const QSizeF label_size( text_size.width() + LabelPadding.width() * 2, text_size.height() + LabelPadding.height() * 2); const float top = rect.height() - label_size.height() - TimeMarker::ArrowSize - 0.5f; const float height = label_size.height(); const pv::util::Timestamp& other_time = other->time(); if (time_ > other_time || (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); } shared_ptr Cursor::get_other_cursor() const { const shared_ptr cursors(view_.cursors()); assert(cursors); return (cursors->first().get() == this) ? cursors->second() : cursors->first(); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/analogsignal.hpp000600 001750 001750 00000014067 13365561737 021251 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP #include #include #include #include #include using std::pair; using std::shared_ptr; namespace pv { namespace data { class Analog; class AnalogSegment; class SignalBase; } namespace views { namespace trace { class AnalogSignal : public Signal { Q_OBJECT private: static const QPen AxisPen; static const QColor SignalColors[4]; static const QColor GridMajorColor, GridMinorColor; static const QColor SamplingPointColor; static const QColor SamplingPointColorLo; static const QColor SamplingPointColorNe; static const QColor SamplingPointColorHi; static const QColor ThresholdColor; static const QColor ThresholdColorLo; static const QColor ThresholdColorNe; static const QColor ThresholdColorHi; static const int64_t TracePaintBlockSize; static const float EnvelopeThreshold; static const int MaximumVDivs; static const int MaxScaleIndex, MinScaleIndex; static const int InfoTextMarginRight, InfoTextMarginBottom; enum DisplayType { DisplayAnalog = 0, DisplayConverted = 1, DisplayBoth = 2 }; public: AnalogSignal(pv::Session &session, shared_ptr base); shared_ptr data() const; virtual void save_settings(QSettings &settings) const; virtual void restore_settings(QSettings &settings); /** * Computes the vertical extents of the contents of this row item. * @return A pair containing the minimum and maximum y-values. */ pair v_extents() const; /** * Paints the background layer of the signal with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with.. */ void paint_back(QPainter &p, ViewItemPaintParams &pp); /** * Paints the mid-layer of the signal with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with.. */ void paint_mid(QPainter &p, ViewItemPaintParams &pp); /** * Paints the foreground layer of the item with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with. */ void paint_fore(QPainter &p, ViewItemPaintParams &pp); private: void paint_grid(QPainter &p, int y, int left, int right); void paint_trace(QPainter &p, const shared_ptr &segment, int y, int left, const int64_t start, const int64_t end, const double pixels_offset, const double samples_per_pixel); void paint_envelope(QPainter &p, const shared_ptr &segment, int y, int left, const int64_t start, const int64_t end, const double pixels_offset, const double samples_per_pixel); void paint_logic_mid(QPainter &p, ViewItemPaintParams &pp); void paint_logic_caps(QPainter &p, QLineF *const lines, vector< pair > &edges, bool level, double samples_per_pixel, double pixels_offset, float x_offset, float y_offset); shared_ptr get_analog_segment_to_paint() const; shared_ptr get_logic_segment_to_paint() const; /** * Computes the scale factor from the scale index and vdiv settings. */ float get_resolution(int scale_index); void update_scale(); 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(); void process_next_sample_value(float x, float value); protected: void populate_popup_form(QWidget *parent, QFormLayout *form); virtual void hover_point_changed(const QPoint &hp); private Q_SLOTS: virtual void on_setting_changed(const QString &key, const QVariant &value); void on_min_max_changed(float min, float max); void on_pos_vdivs_changed(int vdivs); void on_neg_vdivs_changed(int vdivs); void on_div_height_changed(int height); void on_resolution_changed(int index); void on_autoranging_changed(int state); void on_conversion_changed(int index); void on_conv_threshold_changed(int index=-1); void on_delayed_conversion_starter(); void on_display_type_changed(int index); private: QComboBox *resolution_cb_, *conversion_cb_, *conv_threshold_cb_, *display_type_cb_; QSpinBox *pvdiv_sb_, *nvdiv_sb_, *div_height_sb_; float scale_; int scale_index_; int div_height_; int pos_vdivs_, neg_vdivs_; // divs per positive/negative side float resolution_; // e.g. 10 for 10 V/div bool show_analog_minor_grid_; QColor high_fill_color_; bool show_sampling_points_, fill_high_areas_; DisplayType display_type_; bool autoranging_; int conversion_threshold_disp_mode_; vector value_at_pixel_pos_; float value_at_hover_pos_; float prev_value_at_pixel_; // Only used during lookup table update float min_value_at_pixel_, max_value_at_pixel_; // Only used during lookup table update int current_pixel_pos_; // Only used during lookup table update }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP pulseview-0.4.1/pv/views/trace/flag.hpp000600 001750 001750 00000004125 13365561737 017515 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_FLAG_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_FLAG_HPP #include #include "timemarker.hpp" using std::enable_shared_from_this; class QMenu; namespace pv { namespace views { namespace trace { /** * The Flag class represents items on the @ref Ruler that mark important * events on the timeline to the user. They are editable and thus non-static. */ class Flag : public TimeMarker, public enable_shared_from_this { Q_OBJECT public: static const QColor FillColor; public: /** * Constructor. * @param view A reference to the view that owns this cursor pair. * @param time The time to set the flag to. * @param text The text of the marker. */ Flag(View &view, const pv::util::Timestamp& time, const QString &text); /** * Copy constructor. */ Flag(const Flag &flag); /** * Returns true if the item is visible and enabled. */ bool enabled() const; /** * Gets the text to show in the marker. */ QString get_text() const; pv::widgets::Popup* create_popup(QWidget *parent); QMenu* create_header_context_menu(QWidget *parent); void delete_pressed(); private Q_SLOTS: void on_delete(); void on_text_changed(const QString &text); private: QString text_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_FLAG_HPP pulseview-0.4.1/pv/views/trace/cursorpair.hpp000600 001750 001750 00000005767 13365561737 021012 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSORPAIR_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSORPAIR_HPP #include "cursor.hpp" #include "pv/globalsettings.hpp" #include #include #include #include using std::pair; using std::shared_ptr; class QPainter; namespace pv { namespace views { namespace trace { class View; class CursorPair : public TimeItem, public GlobalSettingsInterface { Q_OBJECT private: static const int DeltaPadding; public: /** * Constructor. * @param view A reference to the view that owns this cursor pair. */ CursorPair(View &view); ~CursorPair(); /** * Returns true if the item is visible and enabled. */ bool enabled() const override; /** * Returns a pointer to the first cursor. */ shared_ptr first() const; /** * Returns a pointer to the second cursor. */ shared_ptr second() const; /** * Sets the time of the marker. */ void set_time(const pv::util::Timestamp& time) override; float get_x() const override; QPoint drag_point(const QRect &rect) const override; pv::widgets::Popup* create_popup(QWidget *parent) override; QRectF label_rect(const QRectF &rect) const override; /** * Paints the marker's label to the ruler. * @param p The painter to draw with. * @param rect The rectangle of the ruler client area. * @param hover true if the label is being hovered over by the mouse. */ void paint_label(QPainter &p, const QRect &rect, bool hover) override; /** * Paints the background layer of the item with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with. */ void paint_back(QPainter &p, ViewItemPaintParams &pp) override; /** * Constructs the string to display. */ QString format_string(); pair get_cursor_offsets() const; virtual void on_setting_changed(const QString &key, const QVariant &value) override; public Q_SLOTS: void on_hover_point_changed(const QWidget* widget, const QPoint &hp); private: shared_ptr first_, second_; QColor fill_color_; QSizeF text_size_; QRectF label_area_; bool label_incomplete_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSORPAIR_HPP pulseview-0.4.1/pv/views/trace/tracegroup.cpp000600 001750 001750 00000010573 13365561737 020756 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include "tracegroup.hpp" using std::any_of; using std::pair; using std::shared_ptr; using std::vector; namespace pv { namespace views { namespace trace { const int TraceGroup::Padding = 8; const int TraceGroup::Width = 12; const int TraceGroup::LineThickness = 5; const QColor TraceGroup::LineColor(QColor(0x55, 0x57, 0x53)); TraceGroup::~TraceGroup() { owner_ = nullptr; clear_child_items(); } bool TraceGroup::enabled() const { return any_of(child_items().begin(), child_items().end(), [](const shared_ptr &r) { return r->enabled(); }); } pv::Session& TraceGroup::session() { assert(owner_); return owner_->session(); } const pv::Session& TraceGroup::session() const { assert(owner_); return owner_->session(); } View* TraceGroup::view() { assert(owner_); return owner_->view(); } const View* TraceGroup::view() const { assert(owner_); return owner_->view(); } pair TraceGroup::v_extents() const { return TraceTreeItemOwner::v_extents(); } void TraceGroup::paint_label(QPainter &p, const QRect &rect, bool hover) { const QRectF r = label_rect(rect).adjusted( LineThickness / 2.0, LineThickness / 2.0, -LineThickness / 2.0, -LineThickness / 2.0); // Paint the label const QPointF points[] = { r.topRight(), r.topLeft(), r.bottomLeft(), r.bottomRight() }; if (selected()) { const QPen pen(highlight_pen()); p.setPen(QPen(pen.brush(), pen.width() + LineThickness, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); p.setBrush(Qt::transparent); p.drawPolyline(points, countof(points)); } p.setPen(QPen(QBrush(LineColor.darker()), LineThickness, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); p.drawPolyline(points, countof(points)); p.setPen(QPen(QBrush(hover ? LineColor.lighter() : LineColor), LineThickness - 2, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); p.drawPolyline(points, countof(points)); } QRectF TraceGroup::label_rect(const QRectF &rect) const { QRectF child_rect; for (const shared_ptr& r : child_items()) if (r && r->enabled()) child_rect = child_rect.united(r->label_rect(rect)); return QRectF(child_rect.x() - Width - Padding, child_rect.y(), Width, child_rect.height()); } bool TraceGroup::pt_in_label_rect(int left, int right, const QPoint &point) { (void)left; (void)right; (void)point; return false; } QMenu* TraceGroup::create_header_context_menu(QWidget *parent) { QMenu *const menu = new QMenu(parent); QAction *const ungroup = new QAction(tr("Ungroup"), this); ungroup->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_U)); connect(ungroup, SIGNAL(triggered()), this, SLOT(on_ungroup())); menu->addAction(ungroup); return menu; } pv::widgets::Popup* TraceGroup::create_popup(QWidget *parent) { (void)parent; return nullptr; } int TraceGroup::owner_visual_v_offset() const { return owner_ ? visual_v_offset() + owner_->owner_visual_v_offset() : 0; } unsigned int TraceGroup::depth() const { return owner_ ? owner_->depth() + 1 : 0; } void TraceGroup::ungroup() { const vector> items(trace_tree_child_items()); clear_child_items(); for (const shared_ptr& r : items) owner_->add_child_item(r); owner_->remove_child_item(shared_from_this()); } void TraceGroup::on_ungroup() { ungroup(); } void TraceGroup::row_item_appearance_changed(bool label, bool content) { if (owner_) owner_->row_item_appearance_changed(label, content); } void TraceGroup::extents_changed(bool horz, bool vert) { if (owner_) owner_->extents_changed(horz, vert); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/cursor.hpp000600 001750 001750 00000003447 13365561737 020127 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSOR_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSOR_HPP #include "timemarker.hpp" #include #include using std::shared_ptr; class QPainter; namespace pv { namespace views { namespace trace { class Cursor : public TimeMarker { Q_OBJECT public: static const QColor FillColor; public: /** * Constructor. * @param view A reference to the view that owns this cursor pair. * @param time The time to set the flag to. */ Cursor(View &view, double time); public: /** * Returns true if the item is visible and enabled. */ bool enabled() const; /** * Gets the text to show in the marker. */ QString get_text() const; /** * Gets the marker label rectangle. * @param rect The rectangle of the ruler client area. * @return Returns the label rectangle. */ QRectF label_rect(const QRectF &rect) const; private: shared_ptr get_other_cursor() const; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSOR_HPP pulseview-0.4.1/pv/views/trace/timemarker.cpp000600 001750 001750 00000011732 13365561737 020741 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include "timemarker.hpp" #include "pv/widgets/timestampspinbox.hpp" #include "view.hpp" #include #include #include #include #include using std::max; using std::min; namespace pv { namespace views { namespace trace { const int TimeMarker::ArrowSize = 4; TimeMarker::TimeMarker( View &view, const QColor &color, const pv::util::Timestamp& time) : TimeItem(view), color_(color), time_(time), value_action_(nullptr), value_widget_(nullptr), updating_value_widget_(false) { } const pv::util::Timestamp& TimeMarker::time() const { return time_; } void TimeMarker::set_time(const pv::util::Timestamp& time) { time_ = time; if (value_widget_) { updating_value_widget_ = true; value_widget_->setValue(time); updating_value_widget_ = false; } view_.time_item_appearance_changed(true, true); } float TimeMarker::get_x() const { // Use roundf() from cmath, std::roundf() causes Android issues (see #945). return roundf(((time_ - view_.offset()) / view_.scale()).convert_to()) + 0.5f; } QPoint TimeMarker::drag_point(const QRect &rect) const { (void)rect; return QPoint(get_x(), view_.mapFromGlobal(QCursor::pos()).y()); } QRectF TimeMarker::label_rect(const QRectF &rect) const { QFontMetrics m(QApplication::font()); const QSizeF text_size( max(m.boundingRect(get_text()).size().width(), ArrowSize), m.height()); const QSizeF label_size(text_size + LabelPadding * 2); const float top = rect.height() - label_size.height() - TimeMarker::ArrowSize - 0.5f; const float x = get_x(); return QRectF(QPointF(x - label_size.width() / 2, top), label_size); } QRectF TimeMarker::hit_box_rect(const ViewItemPaintParams &pp) const { const float x = get_x(); const float h = QFontMetrics(QApplication::font()).height(); return QRectF(x - h / 2.0f, pp.top(), h, pp.height()); } void TimeMarker::paint_label(QPainter &p, const QRect &rect, bool hover) { if (!enabled()) return; const qreal x = get_x(); const QRectF r(label_rect(rect)); const QPointF points[] = { r.topLeft(), r.bottomLeft(), QPointF(max(r.left(), x - ArrowSize), r.bottom()), QPointF(x, rect.bottom()), QPointF(min(r.right(), x + ArrowSize), r.bottom()), r.bottomRight(), r.topRight() }; const QPointF highlight_points[] = { QPointF(r.left() + 1, r.top() + 1), QPointF(r.left() + 1, r.bottom() - 1), QPointF(max(r.left() + 1, x - ArrowSize), r.bottom() - 1), QPointF(min(max(r.left() + 1, x), r.right() - 1), rect.bottom() - 1), QPointF(min(r.right() - 1, x + ArrowSize), r.bottom() - 1), QPointF(r.right() - 1, r.bottom() - 1), QPointF(r.right() - 1, r.top() + 1), }; if (selected()) { p.setPen(highlight_pen()); p.setBrush(Qt::transparent); p.drawPolygon(points, countof(points)); } p.setPen(Qt::transparent); p.setBrush(hover ? color_.lighter() : color_); p.drawPolygon(points, countof(points)); p.setPen(color_.lighter()); p.setBrush(Qt::transparent); p.drawPolygon(highlight_points, countof(highlight_points)); p.setPen(color_.darker()); p.setBrush(Qt::transparent); p.drawPolygon(points, countof(points)); p.setPen(select_text_color(color_)); p.drawText(r, Qt::AlignCenter | Qt::AlignVCenter, get_text()); } void TimeMarker::paint_fore(QPainter &p, ViewItemPaintParams &pp) { if (!enabled()) return; const float x = get_x(); p.setPen(color_.darker()); p.drawLine(QPointF(x, pp.top()), QPointF(x, pp.bottom())); } pv::widgets::Popup* TimeMarker::create_popup(QWidget *parent) { using pv::widgets::Popup; Popup *const popup = new Popup(parent); popup->set_position(parent->mapToGlobal( drag_point(parent->rect())), Popup::Bottom); QFormLayout *const form = new QFormLayout(popup); popup->setLayout(form); value_widget_ = new pv::widgets::TimestampSpinBox(parent); value_widget_->setValue(time_); connect(value_widget_, SIGNAL(valueChanged(const pv::util::Timestamp&)), this, SLOT(on_value_changed(const pv::util::Timestamp&))); form->addRow(tr("Time"), value_widget_); return popup; } void TimeMarker::on_value_changed(const pv::util::Timestamp& value) { if (!updating_value_widget_) set_time(value); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/timeitem.cpp000600 001750 001750 00000002461 13365561737 020415 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "signal.hpp" #include "timeitem.hpp" #include "view.hpp" namespace pv { namespace views { namespace trace { TimeItem::TimeItem(View &view) : view_(view) { } void TimeItem::drag_by(const QPoint &delta) { 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 } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/marginwidget.cpp000600 001750 001750 00000003637 13365561737 021267 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include "view.hpp" #include "marginwidget.hpp" #include using std::shared_ptr; namespace pv { namespace views { namespace trace { MarginWidget::MarginWidget(View &parent) : ViewWidget(parent) { setAttribute(Qt::WA_NoSystemBackground, true); } void MarginWidget::item_clicked(const shared_ptr &item) { if (item && item->enabled()) show_popup(item); } void MarginWidget::show_popup(const shared_ptr &item) { pv::widgets::Popup *const p = item->create_popup(this); if (p) p->show(); } void MarginWidget::contextMenuEvent(QContextMenuEvent *event) { event->setAccepted(false); const shared_ptr r = get_mouse_over_item(mouse_point_); if (!r) return; QMenu *menu = r->create_header_context_menu(this); if (menu) { event->setAccepted(true); menu->popup(event->globalPos()); } } void MarginWidget::keyPressEvent(QKeyEvent *event) { assert(event); if (event->key() == Qt::Key_Delete) { const auto items = this->items(); for (auto &i : items) if (i->selected()) i->delete_pressed(); } } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/viewitempaintparams.hpp000600 001750 001750 00000004030 13365561737 022670 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMPAINTPARAMS_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMPAINTPARAMS_HPP #include "pv/util.hpp" #include #include namespace pv { namespace views { namespace trace { class ViewItemPaintParams { public: ViewItemPaintParams( const QRect &rect, double scale, const pv::util::Timestamp& offset); QRect rect() const { return rect_; } double scale() const { return scale_; } const pv::util::Timestamp& offset() const { return offset_; } int left() const { return rect_.left(); } int right() const { return rect_.right(); } int top() const { return rect_.top(); } int bottom() const { return rect_.bottom(); } int width() const { return rect_.width(); } int height() const { return rect_.height(); } double pixels_offset() const { return (offset_ / scale_).convert_to(); } bool next_bg_color_state() { const bool state = bg_color_state_; bg_color_state_ = !bg_color_state_; return state; } public: static QFont font(); static int text_height(); private: QRect rect_; double scale_; pv::util::Timestamp offset_; bool bg_color_state_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMPAINTPARAMS_HPP pulseview-0.4.1/pv/views/trace/timemarker.hpp000600 001750 001750 00000007021 13365561737 020742 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_MARKER_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_MARKER_HPP #include #include #include #include #include #include "timeitem.hpp" class QPainter; class QRect; namespace pv { namespace widgets { class TimestampSpinBox; } namespace views { namespace trace { class View; /** * The TimeMarker class represents items on the @ref Ruler that highlight a * single point in time to the user. Aside from this, it is generic in nature. */ class TimeMarker : public TimeItem { Q_OBJECT public: static const int ArrowSize; protected: /** * Constructor. * @param view A reference to the view that owns this marker. * @param color A reference to the color of this cursor. * @param time The time to set the flag to. */ TimeMarker(View &view, const QColor &color, const pv::util::Timestamp& time); public: /** * Gets the time of the marker. */ const pv::util::Timestamp& time() const; /** * Sets the time of the marker. */ void set_time(const pv::util::Timestamp& time) override; float get_x() const override; /** * Gets the arrow-tip point of the time marker. * @param rect the rectangle of the ruler area. */ QPoint drag_point(const QRect &rect) const override; /** * Computes the outline rectangle of a label. * @param rect the rectangle of the header area. * @return Returns the rectangle of the signal label. */ QRectF label_rect(const QRectF &rect) const override; /** * Computes the outline rectangle of the viewport hit-box. * @param rect the rectangle of the viewport area. * @return Returns the rectangle of the hit-box. */ QRectF hit_box_rect(const ViewItemPaintParams &pp) const override; /** * Gets the text to show in the marker. */ virtual QString get_text() const = 0; /** * Paints the marker's label to the ruler. * @param p The painter to draw with. * @param rect The rectangle of the ruler client area. * @param hover true if the label is being hovered over by the mouse. */ void paint_label(QPainter &p, const QRect &rect, bool hover) override; /** * Paints the foreground layer of the item with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with. */ void paint_fore(QPainter &p, ViewItemPaintParams &pp) override; virtual pv::widgets::Popup* create_popup(QWidget *parent) override; private Q_SLOTS: void on_value_changed(const pv::util::Timestamp& value); protected: const QColor &color_; pv::util::Timestamp time_; QSizeF text_size_; QWidgetAction *value_action_; pv::widgets::TimestampSpinBox *value_widget_; bool updating_value_widget_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_MARKER_HPP pulseview-0.4.1/pv/views/trace/signal.cpp000600 001750 001750 00000007277 13365561737 020067 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include "pv/data/signalbase.hpp" #include "signal.hpp" #include "view.hpp" using std::shared_ptr; namespace pv { namespace views { namespace trace { const char *const ChannelNames[] = { "CLK", "DATA", "IN", "OUT", "RST", "TX", "RX", "EN", "SCLK", "MOSI", "MISO", "/SS", "SDA", "SCL" }; Signal::Signal(pv::Session &session, shared_ptr channel) : Trace(channel), session_(session), name_widget_(nullptr) { assert(base_); connect(base_.get(), SIGNAL(enabled_changed(bool)), this, SLOT(on_enabled_changed(bool))); } void Signal::set_name(QString name) { base_->set_name(name); if (name != name_widget_->currentText()) name_widget_->setEditText(name); } bool Signal::enabled() const { return base_->enabled(); } shared_ptr Signal::base() const { return base_; } void Signal::save_settings(QSettings &settings) const { (void)settings; } void Signal::restore_settings(QSettings &settings) { (void)settings; } void Signal::paint_back(QPainter &p, ViewItemPaintParams &pp) { if (base_->enabled()) Trace::paint_back(p, pp); } void Signal::populate_popup_form(QWidget *parent, QFormLayout *form) { name_widget_ = new QComboBox(parent); name_widget_->setEditable(true); name_widget_->setCompleter(nullptr); for (unsigned int i = 0; i < countof(ChannelNames); i++) name_widget_->insertItem(i, ChannelNames[i]); const int index = name_widget_->findText(base_->name(), Qt::MatchExactly); if (index == -1) { name_widget_->insertItem(0, base_->name()); name_widget_->setCurrentIndex(0); } else { name_widget_->setCurrentIndex(index); } connect(name_widget_, SIGNAL(editTextChanged(const QString&)), this, SLOT(on_nameedit_changed(const QString&))); form->addRow(tr("Name"), name_widget_); add_color_option(parent, form); } QMenu* Signal::create_header_context_menu(QWidget *parent) { QMenu *const menu = Trace::create_header_context_menu(parent); menu->addSeparator(); QAction *const disable = new QAction(tr("Disable"), this); disable->setShortcuts(QKeySequence::Delete); connect(disable, SIGNAL(triggered()), this, SLOT(on_disable())); menu->addAction(disable); return menu; } void Signal::delete_pressed() { on_disable(); } void Signal::on_name_changed(const QString &text) { // On startup, this event is fired when a session restores signal // names. However, the name widget hasn't yet been created. if (!name_widget_) return; if (text != name_widget_->currentText()) name_widget_->setEditText(text); Trace::on_name_changed(text); } void Signal::on_disable() { base_->set_enabled(false); } void Signal::on_enabled_changed(bool enabled) { (void)enabled; if (owner_) owner_->extents_changed(true, true); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/triggermarker.cpp000600 001750 001750 00000003744 13365561737 021452 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "triggermarker.hpp" #include "view.hpp" namespace pv { namespace views { namespace trace { const QColor TriggerMarker::Color(0x00, 0x00, 0xB0); TriggerMarker::TriggerMarker(View &view, const pv::util::Timestamp& time) : TimeItem(view), time_(time) { } TriggerMarker::TriggerMarker(const TriggerMarker &marker) : TimeItem(marker.view_), time_(marker.time_) { } bool TriggerMarker::enabled() const { return true; } bool TriggerMarker::is_draggable(QPoint pos) const { (void)pos; return false; } void TriggerMarker::set_time(const pv::util::Timestamp& time) { time_ = time; view_.time_item_appearance_changed(true, true); } float TriggerMarker::get_x() const { return ((time_ - view_.offset()) / view_.scale()).convert_to(); } QPoint TriggerMarker::drag_point(const QRect &rect) const { (void)rect; // The trigger marker cannot be moved, so there is no drag point return QPoint(INT_MIN, INT_MIN); } void TriggerMarker::paint_fore(QPainter &p, ViewItemPaintParams &pp) { if (!enabled()) return; QPen pen(Color); pen.setStyle(Qt::DashLine); const float x = get_x(); p.setPen(pen); p.drawLine(QPointF(x, pp.top()), QPointF(x, pp.bottom())); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/tracepalette.cpp000600 001750 001750 00000004307 13365561737 021256 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "tracepalette.hpp" namespace pv { namespace views { namespace trace { const QColor TracePalette::Colors[Cols * Rows] = { // Light Colors QColor(0xFC, 0xE9, 0x4F), // Butter QColor(0xFC, 0xAF, 0x3E), // Orange QColor(0xE9, 0xB9, 0x6E), // Chocolate QColor(0x8A, 0xE2, 0x34), // Chameleon QColor(0x72, 0x9F, 0xCF), // Sky Blue QColor(0xAD, 0x7F, 0xA8), // Plum QColor(0xCF, 0x72, 0xC3), // Magenta QColor(0xEF, 0x29, 0x29), // Scarlet Red // Mid Colors QColor(0xED, 0xD4, 0x00), // Butter QColor(0xF5, 0x79, 0x00), // Orange QColor(0xC1, 0x7D, 0x11), // Chocolate QColor(0x73, 0xD2, 0x16), // Chameleon QColor(0x34, 0x65, 0xA4), // Sky Blue QColor(0x75, 0x50, 0x7B), // Plum QColor(0xA3, 0x34, 0x96), // Magenta QColor(0xCC, 0x00, 0x00), // Scarlet Red // Dark Colors QColor(0xC4, 0xA0, 0x00), // Butter QColor(0xCE, 0x5C, 0x00), // Orange QColor(0x8F, 0x59, 0x02), // Chocolate QColor(0x4E, 0x9A, 0x06), // Chameleon QColor(0x20, 0x4A, 0x87), // Sky Blue QColor(0x5C, 0x35, 0x66), // Plum QColor(0x87, 0x20, 0x7A), // Magenta QColor(0xA4, 0x00, 0x00), // Scarlet Red // Greys QColor(0x16, 0x19, 0x1A), // Black QColor(0x2E, 0x34, 0x36), // Grey 1 QColor(0x55, 0x57, 0x53), // Grey 2 QColor(0x88, 0x8A, 0x8F), // Grey 3 QColor(0xBA, 0xBD, 0xB6), // Grey 4 QColor(0xD3, 0xD7, 0xCF), // Grey 5 QColor(0xEE, 0xEE, 0xEC), // Grey 6 QColor(0xFF, 0xFF, 0xFF), // White }; } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/viewwidget.cpp000600 001750 001750 00000016445 13365561737 020765 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include "tracetreeitem.hpp" #include "view.hpp" #include "viewwidget.hpp" using std::any_of; using std::shared_ptr; using std::vector; namespace pv { namespace views { namespace trace { ViewWidget::ViewWidget(View &parent) : QWidget(&parent), view_(parent), item_dragging_(false) { setFocusPolicy(Qt::ClickFocus); setAttribute(Qt::WA_AcceptTouchEvents, true); setMouseTracking(true); } void ViewWidget::clear_selection() { const auto items = this->items(); for (auto &i : items) i->select(false); } void ViewWidget::item_hover(const shared_ptr &item, QPoint pos) { (void)item; (void)pos; } void ViewWidget::item_clicked(const shared_ptr &item) { (void)item; } bool ViewWidget::accept_drag() const { const vector< shared_ptr > items(view_.time_items()); const vector< shared_ptr > trace_tree_items( view_.list_by_type()); const bool any_row_items_selected = any_of( trace_tree_items.begin(), trace_tree_items.end(), [](const shared_ptr &r) { return r->selected(); }); const bool any_time_items_selected = any_of(items.begin(), items.end(), [](const shared_ptr &i) { return i->selected(); }); if (any_row_items_selected && !any_time_items_selected) { // Check all the drag items share a common owner TraceTreeItemOwner *item_owner = nullptr; for (const shared_ptr& r : trace_tree_items) if (r->dragging()) { if (!item_owner) item_owner = r->owner(); else if (item_owner != r->owner()) return false; } return true; } else if (any_time_items_selected && !any_row_items_selected) { return true; } // A background drag is beginning return true; } bool ViewWidget::mouse_down() const { return mouse_down_point_.x() != INT_MIN && mouse_down_point_.y() != INT_MIN; } void ViewWidget::drag_items(const QPoint &delta) { bool item_dragged = false; // Drag the row items const vector< shared_ptr > row_items( view_.list_by_type()); for (const shared_ptr& r : row_items) if (r->dragging()) { r->drag_by(delta); // Ensure the trace is selected r->select(); item_dragged = true; } // If an item is being dragged, update the stacking TraceTreeItemOwner *item_owner = nullptr; const vector< shared_ptr > trace_tree_items( view_.list_by_type()); for (const shared_ptr& i : trace_tree_items) if (i->dragging()) item_owner = i->owner(); if (item_owner) { item_owner->restack_items(); for (shared_ptr i : trace_tree_items) i->animate_to_layout_v_offset(); } // Drag the time items const vector< shared_ptr > items(view_.time_items()); for (auto &i : items) if (i->dragging()) { i->drag_by(delta); item_dragged = true; } // Do the background drag if (!item_dragged) drag_by(delta); } void ViewWidget::drag() { } void ViewWidget::drag_by(const QPoint &delta) { (void)delta; } void ViewWidget::drag_release() { } void ViewWidget::mouse_left_press_event(QMouseEvent *event) { (void)event; const bool ctrl_pressed = QApplication::keyboardModifiers() & Qt::ControlModifier; // Clear selection if control is not pressed and this item is unselected if ((!mouse_down_item_ || !mouse_down_item_->selected()) && !ctrl_pressed) clear_selection(); // Set the signal selection state if the item has been clicked if (mouse_down_item_ && mouse_down_item_->is_selectable(event->pos())) { if (ctrl_pressed) mouse_down_item_->select(!mouse_down_item_->selected()); else mouse_down_item_->select(true); } // Save the offsets of any signals which will be dragged bool item_dragged = false; const auto items = this->items(); for (auto &i : items) if (i->selected() && i->is_draggable(event->pos())) { item_dragged = true; i->drag(); } // Do the background drag if (!item_dragged) drag(); selection_changed(); } void ViewWidget::mouse_left_release_event(QMouseEvent *event) { assert(event); auto items = this->items(); const bool ctrl_pressed = QApplication::keyboardModifiers() & Qt::ControlModifier; // Unselect everything if control is not pressed const shared_ptr mouse_over = get_mouse_over_item(event->pos()); for (auto &i : items) i->drag_release(); if (item_dragging_) view_.restack_all_trace_tree_items(); else { if (!ctrl_pressed) { for (shared_ptr i : items) if (mouse_down_item_ != i) i->select(false); if (mouse_down_item_) item_clicked(mouse_down_item_); } } item_dragging_ = false; } bool ViewWidget::touch_event(QTouchEvent *event) { (void)event; return false; } bool ViewWidget::event(QEvent *event) { switch (event->type()) { case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: if (touch_event(static_cast(event))) return true; break; default: break; } return QWidget::event(event); } void ViewWidget::mousePressEvent(QMouseEvent *event) { assert(event); if (event->button() & Qt::LeftButton) { mouse_down_point_ = event->pos(); mouse_down_item_ = get_mouse_over_item(event->pos()); mouse_left_press_event(event); } /* Don't forward right click events as they will open context menus when * used on trace labels. Those menus prevent ViewWidget::mouseReleaseEvent() * to be triggered upon button release, making mouse_down_item_ * hold the last reference to a view item that might have been deleted * from the context menu, preventing it from being freed as intended. * TODO Remove this once context menus are handled separately */ if (event->button() & Qt::RightButton) mouse_down_point_ = event->pos(); } void ViewWidget::mouseReleaseEvent(QMouseEvent *event) { assert(event); if (event->button() & Qt::LeftButton) mouse_left_release_event(event); mouse_down_point_ = QPoint(INT_MIN, INT_MIN); mouse_down_item_ = nullptr; } void ViewWidget::mouseMoveEvent(QMouseEvent *event) { assert(event); mouse_point_ = event->pos(); if (!event->buttons()) item_hover(get_mouse_over_item(event->pos()), event->pos()); else if (event->buttons() & Qt::LeftButton) { if (!item_dragging_) { if ((event->pos() - mouse_down_point_).manhattanLength() < QApplication::startDragDistance()) return; if (!accept_drag()) return; item_dragging_ = true; } // Do the drag drag_items(event->pos() - mouse_down_point_); } } void ViewWidget::leaveEvent(QEvent*) { mouse_point_ = QPoint(-1, -1); update(); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/trace.cpp000600 001750 001750 00000025645 13365561737 017707 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include "ruler.hpp" #include "trace.hpp" #include "tracepalette.hpp" #include "view.hpp" #include "pv/globalsettings.hpp" #include "pv/widgets/colorbutton.hpp" #include "pv/widgets/popup.hpp" using std::pair; using std::shared_ptr; namespace pv { namespace views { namespace trace { const QPen Trace::AxisPen(QColor(0, 0, 0, 30 * 256 / 100)); const int Trace::LabelHitPadding = 2; const QColor Trace::BrightGrayBGColor = QColor(0, 0, 0, 10 * 255 / 100); const QColor Trace::DarkGrayBGColor = QColor(0, 0, 0, 15 * 255 / 100); Trace::Trace(shared_ptr channel) : base_(channel), axis_pen_(AxisPen), segment_display_mode_(ShowLastSegmentOnly), // Will be overwritten by View current_segment_(0), popup_(nullptr), popup_form_(nullptr) { connect(channel.get(), SIGNAL(name_changed(const QString&)), this, SLOT(on_name_changed(const QString&))); connect(channel.get(), SIGNAL(color_changed(const QColor&)), this, SLOT(on_color_changed(const QColor&))); GlobalSettings::add_change_handler(this); GlobalSettings settings; show_hover_marker_ = settings.value(GlobalSettings::Key_View_ShowHoverMarker).toBool(); } Trace::~Trace() { GlobalSettings::remove_change_handler(this); } shared_ptr Trace::base() const { return base_; } bool Trace::is_selectable(QPoint pos) const { // True if the header was clicked, false if the trace area was clicked const View *view = owner_->view(); assert(view); return (pos.x() <= view->header_width()); } bool Trace::is_draggable(QPoint pos) const { // While the header label that belongs to this trace is draggable, // the trace itself shall not be. Hence we return true if the header // was clicked and false if the trace area was clicked const View *view = owner_->view(); assert(view); return (pos.x() <= view->header_width()); } void Trace::set_segment_display_mode(SegmentDisplayMode mode) { segment_display_mode_ = mode; if (owner_) owner_->row_item_appearance_changed(true, true); } void Trace::on_setting_changed(const QString &key, const QVariant &value) { if (key == GlobalSettings::Key_View_ShowHoverMarker) show_hover_marker_ = value.toBool(); // Force a repaint since many options alter the way traces look if (owner_) owner_->row_item_appearance_changed(false, true); } void Trace::paint_label(QPainter &p, const QRect &rect, bool hover) { const int y = get_visual_y(); p.setBrush(base_->color()); if (!enabled()) return; const QRectF r = label_rect(rect); // When selected, move the arrow to the left so that the border can show const QPointF offs = (selected()) ? QPointF(-2, 0) : QPointF(0, 0); // Paint the label const float label_arrow_length = r.height() / 2; QPointF points[] = { offs + r.topLeft(), offs + QPointF(r.right() - label_arrow_length, r.top()), offs + QPointF(r.right(), y), offs + QPointF(r.right() - label_arrow_length, r.bottom()), offs + r.bottomLeft() }; QPointF highlight_points[] = { offs + QPointF(r.left() + 1, r.top() + 1), offs + QPointF(r.right() - label_arrow_length, r.top() + 1), offs + QPointF(r.right() - 1, y), offs + QPointF(r.right() - label_arrow_length, r.bottom() - 1), offs + QPointF(r.left() + 1, r.bottom() - 1) }; if (selected()) { p.setPen(highlight_pen()); p.setBrush(Qt::transparent); p.drawPolygon(points, countof(points)); } p.setPen(Qt::transparent); p.setBrush(hover ? base_->color().lighter() : base_->color()); p.drawPolygon(points, countof(points)); p.setPen(base_->color().lighter()); p.setBrush(Qt::transparent); p.drawPolygon(highlight_points, countof(highlight_points)); p.setPen(base_->color().darker()); p.setBrush(Qt::transparent); p.drawPolygon(points, countof(points)); // Paint the text p.setPen(select_text_color(base_->color())); p.setFont(QApplication::font()); p.drawText(QRectF(r.x(), r.y(), r.width() - label_arrow_length, r.height()), Qt::AlignCenter | Qt::AlignVCenter, base_->name()); } QMenu* Trace::create_header_context_menu(QWidget *parent) { QMenu *const menu = ViewItem::create_header_context_menu(parent); return menu; } QMenu* Trace::create_view_context_menu(QWidget *parent, QPoint &click_pos) { context_menu_x_pos_ = click_pos.x(); // Get entries from default menu before adding our own QMenu *const menu = new QMenu(parent); QMenu* default_menu = TraceTreeItem::create_view_context_menu(parent, click_pos); if (default_menu) { for (QAction *action : default_menu->actions()) { // clazy:exclude=range-loop menu->addAction(action); if (action->parent() == default_menu) action->setParent(menu); } delete default_menu; // Add separator if needed if (menu->actions().length() > 0) menu->addSeparator(); } QAction *const create_marker_here = new QAction(tr("Create marker here"), this); connect(create_marker_here, SIGNAL(triggered()), this, SLOT(on_create_marker_here())); menu->addAction(create_marker_here); return menu; } pv::widgets::Popup* Trace::create_popup(QWidget *parent) { using pv::widgets::Popup; popup_ = new Popup(parent); popup_->set_position(parent->mapToGlobal( drag_point(parent->rect())), Popup::Right); create_popup_form(); connect(popup_, SIGNAL(closed()), this, SLOT(on_popup_closed())); return popup_; } QRectF Trace::label_rect(const QRectF &rect) const { QFontMetrics m(QApplication::font()); const QSize text_size( m.boundingRect(QRect(), 0, base_->name()).width(), m.height()); const QSizeF label_size( text_size.width() + LabelPadding.width() * 2, ceilf((text_size.height() + LabelPadding.height() * 2) / 2) * 2); const float half_height = label_size.height() / 2; return QRectF( rect.right() - half_height - label_size.width() - 0.5, get_visual_y() + 0.5f - half_height, label_size.width() + half_height, label_size.height()); } QRectF Trace::hit_box_rect(const ViewItemPaintParams &pp) const { // This one is only for the trace itself, excluding the header area const View *view = owner_->view(); assert(view); pair extents = v_extents(); const int top = pp.top() + get_visual_y() + extents.first; const int height = extents.second - extents.first; return QRectF(pp.left() + view->header_width(), top, pp.width() - view->header_width(), height); } void Trace::set_current_segment(const int segment) { current_segment_ = segment; } int Trace::get_current_segment() const { return current_segment_; } void Trace::hover_point_changed(const QPoint &hp) { (void)hp; if (owner_) owner_->row_item_appearance_changed(false, true); } void Trace::paint_back(QPainter &p, ViewItemPaintParams &pp) { const View *view = owner_->view(); assert(view); if (view->colored_bg()) p.setBrush(base_->bgcolor()); else p.setBrush(pp.next_bg_color_state() ? BrightGrayBGColor : DarkGrayBGColor); p.setPen(QPen(Qt::NoPen)); const pair extents = v_extents(); p.drawRect(pp.left(), get_visual_y() + extents.first, pp.width(), extents.second - extents.first); } void Trace::paint_axis(QPainter &p, ViewItemPaintParams &pp, int y) { p.setRenderHint(QPainter::Antialiasing, false); p.setPen(axis_pen_); p.drawLine(QPointF(pp.left(), y), QPointF(pp.right(), y)); p.setRenderHint(QPainter::Antialiasing, true); } void Trace::add_color_option(QWidget *parent, QFormLayout *form) { using pv::widgets::ColorButton; ColorButton *const color_button = new ColorButton( TracePalette::Rows, TracePalette::Cols, parent); color_button->set_palette(TracePalette::Colors); color_button->set_color(base_->color()); connect(color_button, SIGNAL(selected(const QColor&)), this, SLOT(on_coloredit_changed(const QColor&))); form->addRow(tr("Color"), color_button); } void Trace::paint_hover_marker(QPainter &p) { const View *view = owner_->view(); assert(view); const int x = view->hover_point().x(); if (x == -1) return; p.setPen(QPen(QColor(Qt::lightGray))); const pair extents = v_extents(); p.setRenderHint(QPainter::Antialiasing, false); p.drawLine(x, get_visual_y() + extents.first, x, get_visual_y() + extents.second); p.setRenderHint(QPainter::Antialiasing, true); } void Trace::create_popup_form() { // Clear the layout // Transfer the layout and the child widgets to a temporary widget // which we delete after the event was handled. This way, the layout // and all widgets contained therein are deleted after the event was // handled, leaving the parent popup_ time to handle the change. if (popup_form_) { QWidget *suicidal = new QWidget(); suicidal->setLayout(popup_form_); suicidal->deleteLater(); } // Repopulate the popup popup_form_ = new QFormLayout(popup_); popup_->setLayout(popup_form_); populate_popup_form(popup_, popup_form_); } void Trace::populate_popup_form(QWidget *parent, QFormLayout *form) { QLineEdit *const name_edit = new QLineEdit(parent); name_edit->setText(base_->name()); connect(name_edit, SIGNAL(textChanged(const QString&)), this, SLOT(on_nameedit_changed(const QString&))); form->addRow(tr("Name"), name_edit); add_color_option(parent, form); } void Trace::on_name_changed(const QString &text) { /* This event handler is called by SignalBase when the name was changed there */ (void)text; if (owner_) { owner_->extents_changed(true, false); owner_->row_item_appearance_changed(true, false); } } void Trace::on_color_changed(const QColor &color) { /* This event handler is called by SignalBase when the color was changed there */ (void)color; if (owner_) owner_->row_item_appearance_changed(true, true); } void Trace::on_popup_closed() { popup_ = nullptr; popup_form_ = nullptr; } void Trace::on_nameedit_changed(const QString &name) { /* This event handler notifies SignalBase that the name changed */ base_->set_name(name); } void Trace::on_coloredit_changed(const QColor &color) { /* This event handler notifies SignalBase that the color changed */ base_->set_color(color); } void Trace::on_create_marker_here() const { View *view = owner_->view(); assert(view); const Ruler *ruler = view->ruler(); QPoint p = ruler->mapFrom(view, QPoint(context_menu_x_pos_, 0)); view->add_flag(ruler->get_time_from_x_pos(p.x())); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/viewitempaintparams.cpp000600 001750 001750 00000002510 13365561737 022664 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include "viewitempaintparams.hpp" namespace pv { namespace views { namespace trace { ViewItemPaintParams::ViewItemPaintParams( const QRect &rect, double scale, const pv::util::Timestamp& offset) : rect_(rect), scale_(scale), offset_(offset), bg_color_state_(false) { assert(scale > 0.0); } QFont ViewItemPaintParams::font() { return QApplication::font(); } int ViewItemPaintParams::text_height() { return QFontMetrics(font()).height(); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/viewitem.hpp000600 001750 001750 00000010724 13365561737 020437 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWITEM_HPP #define PULSEVIEW_PV_VIEWITEM_HPP #include #include #include #include "viewitempaintparams.hpp" class QAction; class QMenu; class QWidget; namespace pv { namespace widgets { class Popup; } namespace views { namespace trace { class ViewItemOwner; class ViewItem : public QObject { Q_OBJECT public: static const QSizeF LabelPadding; static const int HighlightRadius; public: ViewItem(); /** * Returns true if the item is visible and enabled. */ virtual bool enabled() const = 0; /** * Returns true if the item may be selected. */ virtual bool is_selectable(QPoint pos) const; /** * Returns true if the item has been selected by the user. */ bool selected() const; /** * Selects or deselects the signal. */ virtual void select(bool select = true); /** * Returns true if the item may be dragged/moved. */ virtual bool is_draggable(QPoint pos) const; /** * Returns true if the item is being dragged. */ bool dragging() const; /** * Sets this item into the dragged state. */ void drag(); /** * Sets this item into the un-dragged state. */ virtual void drag_release(); /** * Drags the item to a delta relative to the drag point. * @param delta the offset from the drag point. */ virtual void drag_by(const QPoint &delta) = 0; /** * Get the drag point. * @param rect the rectangle of the widget area. */ virtual QPoint drag_point(const QRect &rect) const = 0; /** * Computes the outline rectangle of a label. * @param rect the rectangle of the header area. * @return Returns the rectangle of the signal label. * @remarks The default implementation returns an empty rectangle. */ virtual QRectF label_rect(const QRectF &rect) const; /** * Computes the outline rectangle of the viewport hit-box. * @param rect the rectangle of the viewport area. * @return Returns the rectangle of the hit-box. * @remarks The default implementation returns an empty hit-box. */ virtual QRectF hit_box_rect(const ViewItemPaintParams &pp) const; /** * Paints the signal label. * @param p the QPainter to paint into. * @param rect the rectangle of the header area. * @param hover true if the label is being hovered over by the mouse. */ virtual void paint_label(QPainter &p, const QRect &rect, bool hover); /** * Paints the background layer of the item with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with. */ virtual void paint_back(QPainter &p, ViewItemPaintParams &pp); /** * Paints the mid-layer of the item with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with. */ virtual void paint_mid(QPainter &p, ViewItemPaintParams &pp); /** * Paints the foreground layer of the item with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with. */ virtual void paint_fore(QPainter &p, ViewItemPaintParams &pp); /** * Gets the text color. * @remarks This color is computed by comparing the lightness * of the trace color against a threshold to determine whether * white or black would be more visible. */ static QColor select_text_color(QColor background); public: virtual QMenu* create_header_context_menu(QWidget *parent); virtual QMenu* create_view_context_menu(QWidget *parent, QPoint &click_pos); virtual pv::widgets::Popup* create_popup(QWidget *parent); virtual void delete_pressed(); protected: static QPen highlight_pen(); protected: QWidget *context_parent_; QPoint drag_point_; private: bool selected_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWITEM_HPP pulseview-0.4.1/pv/views/trace/ruler.hpp000600 001750 001750 00000013510 13365561737 017733 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_RULER_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_RULER_HPP #include #include #include #include "marginwidget.hpp" #include using std::function; using std::pair; using std::shared_ptr; using std::vector; namespace RulerTest { struct tick_position_test_0; struct tick_position_test_1; struct tick_position_test_2; } namespace pv { namespace views { namespace trace { class TimeItem; class ViewItem; struct TickPositions { vector> major; vector minor; }; /** * The Ruler class manages and displays the time scale above the trace canvas. * It may also contain @ref TimeItem instances used to identify or highlight * time-related information. */ class Ruler : public MarginWidget { Q_OBJECT friend struct RulerTest::tick_position_test_0; friend struct RulerTest::tick_position_test_1; friend struct RulerTest::tick_position_test_2; private: /// Height of the ruler in multipes of the text height static const float RulerHeight; /// Height of the hover arrow in multiples of the text height static const float HoverArrowSize; public: Ruler(View &parent); QSize sizeHint() const override; /** * The extended area that the header widget would like to be sized to. * @remarks This area is the area specified by sizeHint, extended by * the area to overlap the viewport. */ QSize extended_size_hint() const override; /** * Formats a timestamp depending on its distance to another timestamp. * * Heuristic function, useful when multiple timestamps should be put side by * side. The function procedes in the following order: * - If 't' is zero, "0" is returned. * - If 'unit' is 'TimeUnit::Samples', 'pv::util::format_time_si_adjusted()' * is used to format 't'. * - If a zoomed out view is detected (determined by 'precision' and * 'distance'), 'pv::util::format_time_minutes() is used. * - For timestamps "near the origin" (determined by 'distance'), * 'pv::util::format_time_si_adjusted()' is used. * - If none of the previous was true, 'pv::util::format_time_minutes()' * is used again. * * @param distance The distance between the timestamp to format and * an adjacent one. * @param t The value to format * @param prefix The SI prefix to use. * @param unit The representation of the timestamp value. * @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. */ static QString format_time_with_distance( const pv::util::Timestamp& distance, const pv::util::Timestamp& t, pv::util::SIPrefix prefix = pv::util::SIPrefix::unspecified, pv::util::TimeUnit unit = pv::util::TimeUnit::Time, unsigned precision = 0, bool sign = true); pv::util::Timestamp get_time_from_x_pos(uint32_t x) const; protected: virtual void contextMenuEvent(QContextMenuEvent *event) override; void resizeEvent(QResizeEvent*) override; private: /** * Gets the time items. */ vector< shared_ptr > items() override; /** * Gets the first view item which has a label that contains @c pt . * @param pt the point to search with. * @return the view item that has been found, or and empty * @c shared_ptr if no item was found. */ shared_ptr get_mouse_over_item(const QPoint &pt) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void paintEvent(QPaintEvent *event) override; /** * Draw a hover arrow under the cursor position. * @param p The painter to draw into. * @param text_height The height of a single text ascent. */ void draw_hover_mark(QPainter &p, int text_height); int calculate_text_height() const; /** * Calculates the major and minor tick positions. * * @param major_period The period between the major ticks. * @param offset The virtual time at the left border of the ruler. * @param scale The scale in seconds per pixel. * @param width the Width of the ruler. * @param format_function A function used to format the major tick times. * @return An object of type 'TickPositions' that contains the major tick * positions together with the labels at that ticks, and the minor * tick positions. */ static TickPositions calculate_tick_positions( const pv::util::Timestamp& major_period, const pv::util::Timestamp& offset, const double scale, const int width, const unsigned int minor_tick_count, function format_function); private Q_SLOTS: void on_hover_point_changed(const QWidget* widget, const QPoint &hp); void invalidate_tick_position_cache(); void on_createMarker(); void on_setZeroPosition(); void on_toggleHoverMarker(); private: /** * Holds the tick positions so that they don't have to be recalculated on * every redraw. Set by 'paintEvent()' when needed. */ boost::optional tick_position_cache_; uint32_t context_menu_x_pos_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_RULER_HPP pulseview-0.4.1/pv/views/trace/view.cpp000600 001750 001750 00000137232 13365561737 017557 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifdef ENABLE_DECODE #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "analogsignal.hpp" #include "header.hpp" #include "logicsignal.hpp" #include "ruler.hpp" #include "signal.hpp" #include "tracegroup.hpp" #include "triggermarker.hpp" #include "view.hpp" #include "viewport.hpp" #include "pv/data/logic.hpp" #include "pv/data/logicsegment.hpp" #include "pv/devices/device.hpp" #include "pv/globalsettings.hpp" #include "pv/session.hpp" #include "pv/util.hpp" #ifdef ENABLE_DECODE #include "decodetrace.hpp" #endif using pv::data::SignalData; using pv::data::Segment; using pv::util::TimeUnit; using pv::util::Timestamp; using std::back_inserter; using std::copy_if; using std::count_if; using std::inserter; using std::max; using std::make_pair; using std::make_shared; using std::min; using std::numeric_limits; 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; namespace pv { namespace views { namespace trace { const Timestamp View::MaxScale("1e9"); const Timestamp View::MinScale("1e-12"); const int View::MaxScrollValue = INT_MAX / 2; const int View::ScaleUnits[3] = {1, 2, 5}; CustomScrollArea::CustomScrollArea(QWidget *parent) : QAbstractScrollArea(parent) { } bool CustomScrollArea::viewportEvent(QEvent *event) { switch (event->type()) { case QEvent::Paint: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::MouseMove: case QEvent::Wheel: case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: return false; default: return QAbstractScrollArea::viewportEvent(event); } } View::View(Session &session, bool is_main_view, QWidget *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() { QVBoxLayout *root_layout = new QVBoxLayout(this); root_layout->setContentsMargins(0, 0, 0, 0); root_layout->addWidget(splitter_); viewport_ = new Viewport(*this); scrollarea_ = new CustomScrollArea(splitter_); scrollarea_->setViewport(viewport_); scrollarea_->setFrameShape(QFrame::NoFrame); ruler_ = new Ruler(*this); header_ = new Header(*this); header_->setMinimumWidth(10); // So that the arrow tips show at least // We put the header into a simple layout so that we can add the top margin, // allowing us to make it line up with the bottom of the ruler QWidget *header_container = new QWidget(); header_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QVBoxLayout *header_layout = new QVBoxLayout(header_container); header_layout->setContentsMargins(0, ruler_->sizeHint().height(), 0, 0); header_layout->addWidget(header_); // To let the ruler and scrollarea be on the same split pane, we need a layout QWidget *trace_container = new QWidget(); trace_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QVBoxLayout *trace_layout = new QVBoxLayout(trace_container); trace_layout->setSpacing(0); // We don't want space between the ruler and scrollarea trace_layout->setContentsMargins(0, 0, 0, 0); trace_layout->addWidget(ruler_); trace_layout->addWidget(scrollarea_); splitter_->addWidget(header_container); splitter_->addWidget(trace_container); 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_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); viewport_->installEventFilter(this); ruler_->installEventFilter(this); header_->installEventFilter(this); // Set up settings and event handlers GlobalSettings settings; colored_bg_ = settings.value(GlobalSettings::Key_View_ColoredBG).toBool(); snap_distance_ = settings.value(GlobalSettings::Key_View_SnapDistance).toInt(); GlobalSettings::add_change_handler(this); connect(scrollarea_->horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(h_scroll_value_changed(int))); connect(scrollarea_->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(v_scroll_value_changed())); connect(header_, SIGNAL(selection_changed()), ruler_, SLOT(clear_selection())); connect(ruler_, SIGNAL(selection_changed()), header_, SLOT(clear_selection())); connect(header_, SIGNAL(selection_changed()), this, SIGNAL(selection_changed())); connect(ruler_, SIGNAL(selection_changed()), this, SIGNAL(selection_changed())); connect(splitter_, SIGNAL(splitterMoved(int, int)), this, SLOT(on_splitter_moved())); connect(&lazy_event_handler_, SIGNAL(timeout()), this, SLOT(process_sticky_events())); lazy_event_handler_.setSingleShot(true); // 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 ruler_->raise(); header_->raise(); reset_view_state(); } View::~View() { GlobalSettings::remove_change_handler(this); } void View::reset_view_state() { ViewBase::reset_view_state(); segment_display_mode_ = Trace::ShowLastSegmentOnly; segment_selectable_ = false; scale_ = 1e-3; offset_ = 0; ruler_offset_ = 0; updating_scroll_ = false; settings_restored_ = false; always_zoom_to_fit_ = false; tick_period_ = 0; tick_prefix_ = pv::util::SIPrefix::yocto; tick_precision_ = 0; time_unit_ = util::TimeUnit::Time; show_cursors_ = false; cursors_ = make_shared(*this); next_flag_text_ = 'A'; trigger_markers_.clear(); hover_widget_ = nullptr; hover_point_ = QPoint(-1, -1); scroll_needs_defaults_ = true; saved_v_offset_ = 0; scale_at_acq_start_ = 0; offset_at_acq_start_ = 0; suppress_zoom_to_fit_after_acq_ = false; show_cursors_ = false; cursor_state_changed(show_cursors_); flags_.clear(); // Update the zoom state calculate_tick_spacing(); // Make sure the standard bar's segment selector is in sync set_segment_display_mode(segment_display_mode_); } Session& View::session() { return session_; } const Session& View::session() const { return session_; } unordered_set< shared_ptr > View::signals() const { return signals_; } void View::clear_signals() { ViewBase::clear_signalbases(); signals_.clear(); } void View::add_signal(const shared_ptr signal) { ViewBase::add_signalbase(signal->base()); signals_.insert(signal); signal->set_segment_display_mode(segment_display_mode_); signal->set_current_segment(current_segment_); connect(signal->base().get(), SIGNAL(name_changed(const QString&)), this, SLOT(on_signal_name_changed())); } #ifdef ENABLE_DECODE void View::clear_decode_signals() { decode_traces_.clear(); } void View::add_decode_signal(shared_ptr signal) { shared_ptr d( new DecodeTrace(session_, signal, decode_traces_.size())); decode_traces_.push_back(d); d->set_segment_display_mode(segment_display_mode_); d->set_current_segment(current_segment_); connect(signal.get(), SIGNAL(name_changed(const QString&)), this, SLOT(on_signal_name_changed())); } void View::remove_decode_signal(shared_ptr signal) { for (auto i = decode_traces_.begin(); i != decode_traces_.end(); i++) if ((*i)->base() == signal) { decode_traces_.erase(i); signals_changed(); return; } } #endif shared_ptr View::get_signal_under_mouse_cursor() const { return signal_under_mouse_cursor_; } View* View::view() { return this; } const View* View::view() const { return this; } Viewport* View::viewport() { return viewport_; } const Viewport* View::viewport() const { return viewport_; } const Ruler* View::ruler() const { return ruler_; } void View::save_settings(QSettings &settings) const { settings.setValue("scale", scale_); settings.setValue("v_offset", scrollarea_->verticalScrollBar()->sliderPosition()); 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("ruler_shift", ruler_shift_); settings.setValue("ruler_shift", QString::fromStdString(ss.str())); } { stringstream ss; boost::archive::text_oarchive oa(ss); oa << boost::serialization::make_nvp("offset", offset_); settings.setValue("offset", QString::fromStdString(ss.str())); } for (const shared_ptr& signal : signals_) { settings.beginGroup(signal->base()->internal_name()); signal->save_settings(settings); settings.endGroup(); } } void View::restore_settings(QSettings &settings) { // Note: It is assumed that this function is only called once, // immediately after restoring a previous session. if (settings.contains("scale")) set_scale(settings.value("scale").toDouble()); if (settings.contains("ruler_shift")) { util::Timestamp shift; stringstream ss; ss << settings.value("ruler_shift").toString().toStdString(); try { boost::archive::text_iarchive ia(ss); ia >> boost::serialization::make_nvp("ruler_shift", shift); ruler_shift_ = shift; } catch (boost::archive::archive_exception&) { qDebug() << "Could not restore the view ruler shift"; } } 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"; } } if (settings.contains("splitter_state")) splitter_->restoreState(settings.value("splitter_state").toByteArray()); if (settings.contains("segment_display_mode")) set_segment_display_mode( (Trace::SegmentDisplayMode)(settings.value("segment_display_mode").toInt())); for (shared_ptr signal : signals_) { settings.beginGroup(signal->base()->internal_name()); signal->restore_settings(settings); settings.endGroup(); } if (settings.contains("v_offset")) { saved_v_offset_ = settings.value("v_offset").toInt(); set_v_offset(saved_v_offset_); scroll_needs_defaults_ = false; // Note: see eventFilter() for additional information } settings_restored_ = true; suppress_zoom_to_fit_after_acq_ = true; // Update the ruler so that it uses the new scale calculate_tick_spacing(); } vector< shared_ptr > View::time_items() const { const vector> f(flags()); vector> items(f.begin(), f.end()); if (cursors_) { items.push_back(cursors_); items.push_back(cursors_->first()); items.push_back(cursors_->second()); } for (auto& trigger_marker : trigger_markers_) items.push_back(trigger_marker); return items; } double View::scale() const { return scale_; } void View::set_scale(double scale) { if (scale_ != scale) { scale_ = scale; scale_changed(); } } void View::set_offset(const pv::util::Timestamp& offset, bool force_update) { if ((offset_ != offset) || force_update) { offset_ = offset; ruler_offset_ = offset_ + ruler_shift_; offset_changed(); } } const Timestamp& View::offset() const { return offset_; } const Timestamp& View::ruler_offset() const { return ruler_offset_; } void View::set_zero_position(const pv::util::Timestamp& position) { // ruler shift is a negative offset and the new zero position is relative // to the current offset. Hence, we adjust the ruler shift only by the // difference. ruler_shift_ = -(position + (-ruler_shift_)); // Force an immediate update of the offsets set_offset(offset_, true); ruler_->update(); } void View::reset_zero_position() { ruler_shift_ = 0; // Force an immediate update of the offsets set_offset(offset_, true); ruler_->update(); } int View::owner_visual_v_offset() const { return -scrollarea_->verticalScrollBar()->sliderPosition(); } void View::set_v_offset(int offset) { scrollarea_->verticalScrollBar()->setSliderPosition(offset); header_->update(); viewport_->update(); } unsigned int View::depth() const { return 0; } uint32_t View::current_segment() const { return current_segment_; } pv::util::SIPrefix View::tick_prefix() const { return tick_prefix_; } void View::set_tick_prefix(pv::util::SIPrefix tick_prefix) { if (tick_prefix_ != tick_prefix) { tick_prefix_ = tick_prefix; tick_prefix_changed(); } } unsigned int View::tick_precision() const { return tick_precision_; } void View::set_tick_precision(unsigned tick_precision) { if (tick_precision_ != tick_precision) { tick_precision_ = tick_precision; tick_precision_changed(); } } const pv::util::Timestamp& View::tick_period() const { return tick_period_; } unsigned int View::minor_tick_count() const { return minor_tick_count_; } void View::set_tick_period(const pv::util::Timestamp& tick_period) { if (tick_period_ != tick_period) { tick_period_ = tick_period; tick_period_changed(); } } TimeUnit View::time_unit() const { return time_unit_; } void View::set_time_unit(pv::util::TimeUnit time_unit) { if (time_unit_ != time_unit) { time_unit_ = time_unit; time_unit_changed(); } } void View::set_current_segment(uint32_t segment_id) { current_segment_ = segment_id; for (const shared_ptr& signal : signals_) signal->set_current_segment(current_segment_); #ifdef ENABLE_DECODE for (shared_ptr& dt : decode_traces_) dt->set_current_segment(current_segment_); #endif vector triggers = session_.get_triggers(current_segment_); trigger_markers_.clear(); for (util::Timestamp timestamp : triggers) trigger_markers_.push_back(make_shared(*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()); viewport_->update(); segment_changed(segment_id); } bool View::segment_is_selectable() const { return segment_selectable_; } Trace::SegmentDisplayMode View::segment_display_mode() const { return segment_display_mode_; } void View::set_segment_display_mode(Trace::SegmentDisplayMode mode) { segment_display_mode_ = mode; for (const shared_ptr& signal : signals_) signal->set_segment_display_mode(mode); uint32_t last_segment = session_.get_segment_count() - 1; switch (mode) { case Trace::ShowLastSegmentOnly: if (current_segment_ != last_segment) set_current_segment(last_segment); break; case Trace::ShowLastCompleteSegmentOnly: // Do nothing if we only have one segment so far if (last_segment > 0) { // If the last segment isn't complete, the previous one must be uint32_t segment_id = (session_.all_segments_complete(last_segment)) ? last_segment : last_segment - 1; if (current_segment_ != segment_id) set_current_segment(segment_id); } break; case Trace::ShowSingleSegmentOnly: case Trace::ShowAllSegments: case Trace::ShowAccumulatedIntensity: default: // Current segment remains as-is break; } segment_selectable_ = true; if ((mode == Trace::ShowAllSegments) || (mode == Trace::ShowAccumulatedIntensity)) segment_selectable_ = false; viewport_->update(); segment_display_mode_changed((int)mode, segment_selectable_); } void View::zoom(double steps) { zoom(steps, viewport_->width() / 2); } void View::zoom(double steps, int offset) { set_zoom(scale_ * pow(3.0 / 2.0, -steps), offset); } void View::zoom_fit(bool gui_state) { // Act as one-shot when stopped, toggle along with the GUI otherwise if (session_.get_capture_state() == Session::Stopped) { always_zoom_to_fit_ = false; always_zoom_to_fit_changed(false); } else { always_zoom_to_fit_ = gui_state; always_zoom_to_fit_changed(gui_state); } const pair extents = get_time_extents(); const Timestamp delta = extents.second - extents.first; if (delta < Timestamp("1e-12")) return; assert(viewport_); const int w = viewport_->width(); if (w <= 0) return; const Timestamp scale = max(min(delta / w, MaxScale), MinScale); set_scale_offset(scale.convert_to(), extents.first); } 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 if ((scale_ == scale) && (offset_ != offset) && (session_.get_capture_state() == Session::Running)) { if (sticky_scrolling_) { sticky_scrolling_ = false; sticky_scrolling_changed(false); } if (always_zoom_to_fit_) { always_zoom_to_fit_ = false; always_zoom_to_fit_changed(false); } } set_scale(scale); set_offset(offset); calculate_tick_spacing(); update_scroll(); ruler_->update(); viewport_->update(); } set< shared_ptr > View::get_visible_data() const { // Make a set of all the visible data objects set< shared_ptr > visible_data; for (const shared_ptr& sig : signals_) if (sig->enabled()) visible_data.insert(sig->data()); return visible_data; } pair View::get_time_extents() const { boost::optional left_time, right_time; const set< shared_ptr > visible_data = get_visible_data(); for (const shared_ptr& d : visible_data) { const vector< shared_ptr > segments = d->segments(); for (const shared_ptr& s : segments) { double samplerate = s->samplerate(); samplerate = (samplerate <= 0.0) ? 1.0 : 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 || !right_time) return make_pair(0, 0); assert(*left_time < *right_time); 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_; } bool View::cursors_shown() const { return show_cursors_; } void View::show_cursors(bool show) { show_cursors_ = show; cursor_state_changed(show); ruler_->update(); viewport_->update(); } void View::centre_cursors() { if (cursors_) { const double time_width = scale_ * viewport_->width(); cursors_->first()->set_time(offset_ + time_width * 0.4); cursors_->second()->set_time(offset_ + time_width * 0.6); ruler_->update(); viewport_->update(); } } shared_ptr View::cursors() const { return cursors_; } void View::add_flag(const Timestamp& time) { flags_.push_back(make_shared(*this, time, QString("%1").arg(next_flag_text_))); next_flag_text_ = (next_flag_text_ >= 'Z') ? 'A' : (next_flag_text_ + 1); time_item_appearance_changed(true, true); } void View::remove_flag(shared_ptr flag) { flags_.remove(flag); time_item_appearance_changed(true, true); } vector< shared_ptr > View::flags() const { vector< shared_ptr > flags(flags_.begin(), flags_.end()); stable_sort(flags.begin(), flags.end(), [](const shared_ptr &a, const shared_ptr &b) { return a->time() < b->time(); }); return flags; } const QPoint& View::hover_point() const { return hover_point_; } const QWidget* View::hover_widget() const { return hover_widget_; } int64_t View::get_nearest_level_change(const QPoint &p) { // Is snapping disabled? if (snap_distance_ == 0) return -1; struct entry_t { entry_t(shared_ptr s) : signal(s), delta(numeric_limits::max()), sample(-1), is_dense(false) {} shared_ptr signal; int64_t delta; int64_t sample; bool is_dense; }; vector list; // Create list of signals to consider if (signal_under_mouse_cursor_) list.emplace_back(signal_under_mouse_cursor_); else for (shared_ptr s : signals_) { if (!s->enabled()) continue; list.emplace_back(s); } // Get data for listed signals for (entry_t &e : list) { // Calculate sample number from cursor position const double samples_per_pixel = e.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); vector edges = e.signal->get_nearest_level_changes(sample_num); if (edges.empty()) continue; // Check first edge const int64_t first_sample_delta = abs(sample_num - edges.front().first); const int64_t first_delta = first_sample_delta / samples_per_pixel; e.delta = first_delta; e.sample = edges.front().first; // Check second edge if available if (edges.size() == 2) { // Note: -1 because this is usually the right edge and sample points are left-aligned const int64_t second_sample_delta = abs(sample_num - edges.back().first - 1); const int64_t second_delta = second_sample_delta / samples_per_pixel; // If both edges are too close, we mark this signal as being dense if ((first_delta + second_delta) <= snap_distance_) e.is_dense = true; if (second_delta < first_delta) { e.delta = second_delta; e.sample = edges.back().first; } } } // Look for the best match: non-dense first, then dense entry_t *match = nullptr; for (entry_t &e : list) { if (e.delta > snap_distance_ || e.is_dense) continue; if (match) { if (e.delta < match->delta) match = &e; } else match = &e; } if (!match) { for (entry_t &e : list) { if (!e.is_dense) continue; if (match) { if (e.delta < match->delta) match = &e; } else match = &e; } } if (match) { // Somewhat ugly hack to make TimeItem::drag_by() work signal_under_mouse_cursor_ = match->signal; return match->sample; } return -1; } void View::restack_all_trace_tree_items() { // Make a list of owners that is sorted from deepest first const vector> items( list_by_type()); set< TraceTreeItemOwner* > owners; for (const auto &r : items) owners.insert(r->owner()); vector< TraceTreeItemOwner* > sorted_owners(owners.begin(), owners.end()); sort(sorted_owners.begin(), sorted_owners.end(), [](const TraceTreeItemOwner* a, const TraceTreeItemOwner *b) { return a->depth() > b->depth(); }); // Restack the items recursively for (auto &o : sorted_owners) o->restack_items(); // Animate the items to their destination for (const auto &i : items) i->animate_to_layout_v_offset(); } int View::header_width() const { return header_->extended_size_hint().width(); } void View::on_setting_changed(const QString &key, const QVariant &value) { if (key == GlobalSettings::Key_View_TriggerIsZeroTime) on_settingViewTriggerIsZeroTime_changed(value); if (key == GlobalSettings::Key_View_SnapDistance) { GlobalSettings settings; snap_distance_ = settings.value(GlobalSettings::Key_View_SnapDistance).toInt(); } } void View::trigger_event(int segment_id, util::Timestamp location) { // TODO This doesn't work if we're showing multiple segments at once 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); trigger_markers_.push_back(make_shared(*this, location)); } void View::get_scroll_layout(double &length, Timestamp &offset) const { const pair extents = get_time_extents(); length = ((extents.second - extents.first) / scale_).convert_to(); offset = offset_ / scale_; } void View::set_zoom(double scale, int offset) { // Reset the "always zoom to fit" feature as the user changed the zoom always_zoom_to_fit_ = false; always_zoom_to_fit_changed(false); 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(), new_offset); } void View::calculate_tick_spacing() { const double SpacingIncrement = 10.0f; const double MinValueSpacing = 40.0f; // Figure out the highest numeric value visible on a label const QSize areaSize = viewport_->size(); const Timestamp max_time = max(fabs(offset_), fabs(offset_ + scale_ * areaSize.width())); double min_width = SpacingIncrement; double label_width, tick_period_width; QFontMetrics m(QApplication::font()); // Copies of the member variables with the same name, used in the calculation // and written back afterwards, so that we don't emit signals all the time // during the calculation. pv::util::Timestamp tick_period = tick_period_; pv::util::SIPrefix tick_prefix = tick_prefix_; unsigned tick_precision = tick_precision_; do { const double min_period = scale_ * min_width; const int order = (int)floorf(log10f(min_period)); const pv::util::Timestamp order_decimal = pow(pv::util::Timestamp(10), order); // Allow for a margin of error so that a scale unit of 1 can be used. // Otherwise, for a SU of 1 the tick period will almost always be below // the min_period by a small amount - and thus skipped in favor of 2. // Note: margin assumes that SU[0] and SU[1] contain the smallest values double tp_margin = (ScaleUnits[0] + ScaleUnits[1]) / 2.0; double tp_with_margin; unsigned int unit = 0; do { tp_with_margin = order_decimal.convert_to() * (ScaleUnits[unit++] + tp_margin); } while (tp_with_margin < min_period && unit < countof(ScaleUnits)); minor_tick_count_ = (unit == 2) ? 4 : 5; tick_period = order_decimal * ScaleUnits[unit - 1]; tick_prefix = static_cast( (order - pv::util::exponent(pv::util::SIPrefix::yocto)) / 3); // Precision is the number of fractional digits required, not // taking the prefix into account (and it must never be negative) tick_precision = max(ceil(log10(1 / tick_period)).convert_to(), 0); tick_period_width = (tick_period / scale_).convert_to(); const QString label_text = Ruler::format_time_with_distance( tick_period, max_time, tick_prefix, time_unit_, tick_precision); label_width = m.boundingRect(0, 0, INT_MAX, INT_MAX, Qt::AlignLeft | Qt::AlignTop, label_text).width() + MinValueSpacing; min_width += SpacingIncrement; } while (tick_period_width < label_width); set_tick_period(tick_period); set_tick_prefix(tick_prefix); set_tick_precision(tick_precision); } void View::adjust_top_margin() { assert(viewport_); const QSize areaSize = viewport_->size(); const pair extents = v_extents(); const int top_margin = owner_visual_v_offset() + extents.first; const int trace_bottom = owner_visual_v_offset() + extents.first + extents.second; // Do we have empty space at the top while the last trace goes out of screen? if ((top_margin > 0) && (trace_bottom > areaSize.height())) { const int trace_height = extents.second - extents.first; // Center everything vertically if there is enough space if (areaSize.height() >= trace_height) set_v_offset(extents.first - ((areaSize.height() - trace_height) / 2)); else // Remove the top margin to make as many traces fit on screen as possible set_v_offset(extents.first); } } void View::update_scroll() { assert(viewport_); QScrollBar *hscrollbar = scrollarea_->horizontalScrollBar(); QScrollBar *vscrollbar = scrollarea_->verticalScrollBar(); const QSize areaSize = viewport_->size(); // Set the horizontal scroll bar double length = 0; Timestamp offset; get_scroll_layout(length, offset); length = max(length - areaSize.width(), 0.0); int major_tick_distance = (tick_period_ / scale_).convert_to(); hscrollbar->setPageStep(areaSize.width() / 2); hscrollbar->setSingleStep(major_tick_distance); updating_scroll_ = true; if (length < MaxScrollValue) { hscrollbar->setRange(0, length); hscrollbar->setSliderPosition(offset.convert_to()); } else { hscrollbar->setRange(0, MaxScrollValue); hscrollbar->setSliderPosition( (offset_ * MaxScrollValue / (scale_ * length)).convert_to()); } updating_scroll_ = false; // Set the vertical scrollbar vscrollbar->setPageStep(areaSize.height()); vscrollbar->setSingleStep(areaSize.height() / 8); const pair 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 (scroll_needs_defaults_) { set_scroll_default(); scroll_needs_defaults_ = false; } } void View::reset_scroll() { scrollarea_->verticalScrollBar()->setRange(0, 0); } void View::set_scroll_default() { assert(viewport_); const QSize areaSize = viewport_->size(); const pair extents = v_extents(); const int trace_height = extents.second - extents.first; // Do all traces fit in the view? if (areaSize.height() >= trace_height) // Center all traces vertically set_v_offset(extents.first - ((areaSize.height() - trace_height) / 2)); else // Put the first trace at the top, letting the bottom ones overflow set_v_offset(extents.first); } void View::determine_if_header_was_shrunk() { const int header_pane_width = splitter_->sizes().front(); // clazy:exclude=detaching-temporary // Allow for a slight margin of error so that we also accept // slight differences when e.g. a label name change increased // the overall width header_was_shrunk_ = (header_pane_width < (header_width() - 10)); } void View::resize_header_to_fit() { // Setting the maximum width of the header widget doesn't work as // expected because the splitter would allow the user to make the // pane wider than that, creating empty space as a result. // To make this work, we stricly enforce the maximum width by // expanding the header unless the user shrunk it on purpose. // As we're then setting the width of the header pane, we set the // splitter to the maximum allowed position. int splitter_area_width = 0; for (int w : splitter_->sizes()) // clazy:exclude=range-loop splitter_area_width += w; // Make sure the header has enough horizontal space to show all labels fully QList pane_sizes; pane_sizes.push_back(header_->extended_size_hint().width()); pane_sizes.push_back(splitter_area_width - header_->extended_size_hint().width()); splitter_->setSizes(pane_sizes); } void View::update_layout() { update_scroll(); } TraceTreeItemOwner* View::find_prevalent_trace_group( const shared_ptr &group, const unordered_map, shared_ptr > &signal_map) { assert(group); unordered_set owners; vector owner_list; // Make a set and a list of all the owners for (const auto& channel : group->channels()) { for (auto& entry : signal_map) { if (entry.first->channel() == channel) { TraceTreeItemOwner *const o = (entry.second)->owner(); owner_list.push_back(o); owners.insert(o); } } } // Iterate through the list of owners, and find the most prevalent size_t max_prevalence = 0; TraceTreeItemOwner *prevalent_owner = nullptr; for (TraceTreeItemOwner *owner : owners) { const size_t prevalence = count_if( owner_list.begin(), owner_list.end(), [&](TraceTreeItemOwner *o) { return o == owner; }); if (prevalence > max_prevalence) { max_prevalence = prevalence; prevalent_owner = owner; } } return prevalent_owner; } vector< shared_ptr > View::extract_new_traces_for_channels( const vector< shared_ptr > &channels, const unordered_map, shared_ptr > &signal_map, set< shared_ptr > &add_list) { vector< shared_ptr > filtered_traces; for (const auto& channel : channels) { for (auto& entry : signal_map) { if (entry.first->channel() == channel) { shared_ptr trace = entry.second; const auto list_iter = add_list.find(trace); if (list_iter == add_list.end()) continue; filtered_traces.push_back(trace); add_list.erase(list_iter); } } } return filtered_traces; } void View::determine_time_unit() { // Check whether we know the sample rate and hence can use time as the unit if (time_unit_ == util::TimeUnit::Samples) { // Check all signals but... for (const shared_ptr& signal : signals_) { const shared_ptr data = signal->data(); // ...only check first segment of each const vector< shared_ptr > segments = data->segments(); if (!segments.empty()) if (segments[0]->samplerate()) { set_time_unit(util::TimeUnit::Time); break; } } } } bool View::eventFilter(QObject *object, QEvent *event) { const QEvent::Type type = event->type(); if (type == QEvent::MouseMove) { if (object) hover_widget_ = qobject_cast(object); const QMouseEvent *const mouse_event = (QMouseEvent*)event; if (object == viewport_) hover_point_ = mouse_event->pos(); else if (object == ruler_) hover_point_ = mouse_event->pos(); else if (object == header_) hover_point_ = QPoint(0, mouse_event->y()); else hover_point_ = QPoint(-1, -1); update_hover_point(); } else if (type == QEvent::Leave) { hover_point_ = QPoint(-1, -1); update_hover_point(); } else if (type == QEvent::Show) { // This is somewhat of a hack, unfortunately. We cannot use // set_v_offset() from within restore_settings() as the view // at that point is neither visible nor properly sized. // This is the least intrusive workaround I could come up // with: set the vertical offset (or scroll defaults) when // the view is shown, which happens after all widgets were // resized to their final sizes. update_layout(); if (settings_restored_) determine_if_header_was_shrunk(); else resize_header_to_fit(); if (scroll_needs_defaults_) { set_scroll_default(); scroll_needs_defaults_ = false; } if (saved_v_offset_) { set_v_offset(saved_v_offset_); saved_v_offset_ = 0; } } return QObject::eventFilter(object, event); } void View::contextMenuEvent(QContextMenuEvent *event) { QPoint pos = event->pos() - QPoint(0, ruler_->sizeHint().height()); const shared_ptr r = viewport_->get_mouse_over_item(pos); if (!r) return; QMenu *menu = r->create_view_context_menu(this, pos); if (menu) menu->popup(event->globalPos()); } void View::resizeEvent(QResizeEvent* event) { // Only adjust the top margin if we shrunk vertically if (event->size().height() < event->oldSize().height()) adjust_top_margin(); update_layout(); } void View::update_hover_point() { // Determine signal that the mouse cursor is hovering over signal_under_mouse_cursor_.reset(); if (hover_widget_ == this) { for (const 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 (const shared_ptr& r : trace_tree_items) r->hover_point_changed(hover_point_); // Notify any other listeners hover_point_changed(hover_widget_, hover_point_); } void View::row_item_appearance_changed(bool label, bool content) { if (label) header_->update(); if (content) viewport_->update(); } void View::time_item_appearance_changed(bool label, bool content) { if (label) { ruler_->update(); // Make sure the header pane width is updated, too update_layout(); } if (content) viewport_->update(); } void View::extents_changed(bool horz, bool vert) { sticky_events_ |= (horz ? TraceTreeItemHExtentsChanged : 0) | (vert ? TraceTreeItemVExtentsChanged : 0); lazy_event_handler_.stop(); lazy_event_handler_.start(); } void View::on_signal_name_changed() { if (!header_was_shrunk_) resize_header_to_fit(); } void View::on_splitter_moved() { // The header can only shrink when the splitter is moved manually determine_if_header_was_shrunk(); if (!header_was_shrunk_) resize_header_to_fit(); } void View::h_scroll_value_changed(int value) { if (updating_scroll_) return; // Disable sticky scrolling when user moves the horizontal scroll bar // during a running acquisition if (sticky_scrolling_ && (session_.get_capture_state() == Session::Running)) { sticky_scrolling_ = false; sticky_scrolling_changed(false); } const int range = scrollarea_->horizontalScrollBar()->maximum(); if (range < MaxScrollValue) set_offset(scale_ * value); else { double length = 0; Timestamp offset; get_scroll_layout(length, offset); set_offset(scale_ * length * value / MaxScrollValue); } ruler_->update(); viewport_->update(); } void View::v_scroll_value_changed() { header_->update(); viewport_->update(); } void View::signals_changed() { using sigrok::Channel; vector< shared_ptr > channels; shared_ptr sr_dev; bool signals_added_or_removed = false; // Do we need to set the vertical scrollbar to its default position later? // We do if there are no traces, i.e. the scroll bar has no range set bool reset_scrollbar = (scrollarea_->verticalScrollBar()->minimum() == scrollarea_->verticalScrollBar()->maximum()); if (!session_.device()) { reset_scroll(); signals_.clear(); } else { sr_dev = session_.device()->device(); assert(sr_dev); channels = sr_dev->channels(); } vector< shared_ptr > new_top_level_items; // Make a list of traces that are being added, and a list of traces // that are being removed const vector> prev_trace_list = list_by_type(); const set> prev_traces( prev_trace_list.begin(), prev_trace_list.end()); set< shared_ptr > traces(signals_.begin(), signals_.end()); #ifdef ENABLE_DECODE traces.insert(decode_traces_.begin(), decode_traces_.end()); #endif set< shared_ptr > add_traces; set_difference(traces.begin(), traces.end(), prev_traces.begin(), prev_traces.end(), inserter(add_traces, add_traces.begin())); set< shared_ptr > remove_traces; set_difference(prev_traces.begin(), prev_traces.end(), traces.begin(), traces.end(), inserter(remove_traces, remove_traces.begin())); // Make a look-up table of sigrok Channels to pulseview Signals unordered_map, shared_ptr > signal_map; for (const shared_ptr& sig : signals_) signal_map[sig->base()] = sig; // Populate channel groups if (sr_dev) for (auto& entry : sr_dev->channel_groups()) { const shared_ptr &group = entry.second; if (group->channels().size() <= 1) continue; // Find best trace group to add to TraceTreeItemOwner *owner = find_prevalent_trace_group( group, signal_map); // If there is no trace group, create one shared_ptr new_trace_group; if (!owner) { new_trace_group.reset(new TraceGroup()); owner = new_trace_group.get(); } // Extract traces for the trace group, removing them from // the add list const vector< shared_ptr > new_traces_in_group = extract_new_traces_for_channels(group->channels(), signal_map, add_traces); // Add the traces to the group const pair prev_v_extents = owner->v_extents(); int offset = prev_v_extents.second - prev_v_extents.first; for (const shared_ptr& trace : new_traces_in_group) { assert(trace); owner->add_child_item(trace); const pair extents = trace->v_extents(); if (trace->enabled()) offset += -extents.first; trace->force_to_v_offset(offset); if (trace->enabled()) offset += extents.second; } if (new_trace_group) { // Assign proper vertical offsets to each channel in the group new_trace_group->restack_items(); // If this is a new group, enqueue it in the new top level // items list if (!new_traces_in_group.empty()) new_top_level_items.push_back(new_trace_group); } } // Enqueue the remaining logic channels in a group vector< shared_ptr > logic_channels; copy_if(channels.begin(), channels.end(), back_inserter(logic_channels), [](const shared_ptr& c) { return c->type() == sigrok::ChannelType::LOGIC; }); const vector< shared_ptr > non_grouped_logic_signals = extract_new_traces_for_channels(logic_channels, signal_map, add_traces); if (non_grouped_logic_signals.size() > 0) { const shared_ptr non_grouped_trace_group( make_shared()); for (const shared_ptr& trace : non_grouped_logic_signals) non_grouped_trace_group->add_child_item(trace); non_grouped_trace_group->restack_items(); new_top_level_items.push_back(non_grouped_trace_group); } // Enqueue the remaining channels as free ungrouped traces const vector< shared_ptr > new_top_level_signals = extract_new_traces_for_channels(channels, signal_map, add_traces); new_top_level_items.insert(new_top_level_items.end(), new_top_level_signals.begin(), new_top_level_signals.end()); // Enqueue any remaining traces i.e. decode traces new_top_level_items.insert(new_top_level_items.end(), add_traces.begin(), add_traces.end()); // Remove any removed traces for (const shared_ptr& trace : remove_traces) { TraceTreeItemOwner *const owner = trace->owner(); assert(owner); owner->remove_child_item(trace); signals_added_or_removed = true; } // Remove any empty trace groups for (shared_ptr group : list_by_type()) if (group->child_items().size() == 0) { remove_child_item(group); group.reset(); } // Add and position the pending top levels items int offset = v_extents().second; for (auto item : new_top_level_items) { add_child_item(item); // Position the item after the last item or at the top if there is none const pair extents = item->v_extents(); if (item->enabled()) offset += -extents.first; item->force_to_v_offset(offset); if (item->enabled()) offset += extents.second; signals_added_or_removed = true; } if (signals_added_or_removed && !header_was_shrunk_) resize_header_to_fit(); update_layout(); header_->update(); viewport_->update(); if (reset_scrollbar) set_scroll_default(); } void View::capture_state_updated(int state) { GlobalSettings settings; if (state == Session::Running) { set_time_unit(util::TimeUnit::Samples); trigger_markers_.clear(); scale_at_acq_start_ = scale_; offset_at_acq_start_ = offset_; // Activate "always zoom to fit" if the setting is enabled and we're // the main view of this session (other trace views may be used for // zooming and we don't want to mess them up) bool state = settings.value(GlobalSettings::Key_View_ZoomToFitDuringAcq).toBool(); if (is_main_view_ && state) { always_zoom_to_fit_ = true; always_zoom_to_fit_changed(always_zoom_to_fit_); } // Enable sticky scrolling if the setting is enabled sticky_scrolling_ = settings.value(GlobalSettings::Key_View_StickyScrolling).toBool(); // Reset all traces to segment 0 current_segment_ = 0; set_current_segment(current_segment_); } if (state == Session::Stopped) { // After acquisition has stopped we need to re-calculate the ticks once // as it's otherwise done when the user pans or zooms, which is too late calculate_tick_spacing(); // Reset "always zoom to fit", the acquisition has stopped if (always_zoom_to_fit_) { // Perform a final zoom-to-fit before disabling zoom_fit(always_zoom_to_fit_); always_zoom_to_fit_ = false; always_zoom_to_fit_changed(always_zoom_to_fit_); } bool zoom_to_fit_after_acq = settings.value(GlobalSettings::Key_View_ZoomToFitAfterAcq).toBool(); // Only perform zoom-to-fit if the user hasn't altered the viewport and // we didn't restore settings in the meanwhile if (zoom_to_fit_after_acq && !suppress_zoom_to_fit_after_acq_ && (scale_ == scale_at_acq_start_) && (offset_ == offset_at_acq_start_)) zoom_fit(false); // We're stopped, so the GUI state doesn't matter suppress_zoom_to_fit_after_acq_ = false; } } void View::on_new_segment(int new_segment_id) { on_segment_changed(new_segment_id); } void View::on_segment_completed(int segment_id) { on_segment_changed(segment_id); } void View::on_segment_changed(int segment) { switch (segment_display_mode_) { case Trace::ShowLastSegmentOnly: case Trace::ShowSingleSegmentOnly: set_current_segment(segment); break; case Trace::ShowLastCompleteSegmentOnly: // Only update if all segments are complete if (session_.all_segments_complete(segment)) set_current_segment(segment); break; case Trace::ShowAllSegments: case Trace::ShowAccumulatedIntensity: default: break; } } 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 triggers = session_.get_triggers(current_segment_); if (triggers.size() > 0) set_zero_position(triggers.front()); } else reset_zero_position(); } void View::perform_delayed_view_update() { if (always_zoom_to_fit_) { zoom_fit(true); } else if (sticky_scrolling_) { // Make right side of the view sticky double length = 0; Timestamp offset; get_scroll_layout(length, offset); const QSize areaSize = viewport_->size(); length = max(length - areaSize.width(), 0.0); set_offset(scale_ * length); } determine_time_unit(); update_scroll(); ruler_->update(); viewport_->update(); } void View::process_sticky_events() { if (sticky_events_ & TraceTreeItemHExtentsChanged) update_layout(); if (sticky_events_ & TraceTreeItemVExtentsChanged) { restack_all_trace_tree_items(); update_scroll(); } // Clear the sticky events sticky_events_ = 0; } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/flag.cpp000600 001750 001750 00000005114 13365561737 017507 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "timemarker.hpp" #include "view.hpp" #include #include #include #include #include #include using std::enable_shared_from_this; using std::shared_ptr; namespace pv { namespace views { namespace trace { const QColor Flag::FillColor(0x73, 0xD2, 0x16); Flag::Flag(View &view, const pv::util::Timestamp& time, const QString &text) : TimeMarker(view, FillColor, time), text_(text) { } Flag::Flag(const Flag &flag) : TimeMarker(flag.view_, FillColor, flag.time_), enable_shared_from_this(flag) { } bool Flag::enabled() const { return true; } QString Flag::get_text() const { return text_; } pv::widgets::Popup* Flag::create_popup(QWidget *parent) { using pv::widgets::Popup; Popup *const popup = TimeMarker::create_popup(parent); popup->set_position(parent->mapToGlobal( drag_point(parent->rect())), Popup::Bottom); QFormLayout *const form = (QFormLayout*)popup->layout(); QLineEdit *const text_edit = new QLineEdit(popup); text_edit->setText(text_); connect(text_edit, SIGNAL(textChanged(const QString&)), this, SLOT(on_text_changed(const QString&))); form->insertRow(0, tr("Text"), text_edit); return popup; } QMenu* Flag::create_header_context_menu(QWidget *parent) { QMenu *const menu = new QMenu(parent); QAction *const del = new QAction(tr("Delete"), this); del->setShortcuts(QKeySequence::Delete); connect(del, SIGNAL(triggered()), this, SLOT(on_delete())); menu->addAction(del); return menu; } void Flag::delete_pressed() { on_delete(); } void Flag::on_delete() { view_.remove_flag(shared_ptr(shared_from_this())); } void Flag::on_text_changed(const QString &text) { text_ = text; view_.time_item_appearance_changed(true, false); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/header.cpp000600 001750 001750 00000013311 13365561737 020024 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "header.hpp" #include "view.hpp" #include "signal.hpp" #include "tracegroup.hpp" #include #include #include #include #include #include #include #include #include #include using boost::make_filter_iterator; using std::count_if; using std::dynamic_pointer_cast; using std::shared_ptr; using std::stable_sort; using std::vector; namespace pv { namespace views { namespace trace { const int Header::Padding = 12; static bool item_selected(shared_ptr r) { return r->selected(); } Header::Header(View &parent) : MarginWidget(parent) { } QSize Header::sizeHint() const { QRectF max_rect(-Padding, 0, Padding, 0); const vector> items( view_.list_by_type()); for (auto &i : items) if (i->enabled()) max_rect = max_rect.united(i->label_rect(QRect())); return QSize(max_rect.width() + Padding, 0); } QSize Header::extended_size_hint() const { return sizeHint() + QSize(ViewItem::HighlightRadius, 0); } vector< shared_ptr > Header::items() { const vector> items( view_.list_by_type()); return vector< shared_ptr >(items.begin(), items.end()); } shared_ptr Header::get_mouse_over_item(const QPoint &pt) { const QRect r(0, 0, width(), height()); const vector> items( view_.list_by_type()); for (auto i = items.rbegin(); i != items.rend(); i++) if ((*i)->enabled() && (*i)->label_rect(r).contains(pt)) return *i; return shared_ptr(); } void Header::paintEvent(QPaintEvent*) { const QRect rect(0, 0, width(), height()); vector< shared_ptr > items(view_.list_by_type()); stable_sort(items.begin(), items.end(), [](const shared_ptr &a, const shared_ptr &b) { return a->drag_point(QRect()).y() < b->drag_point(QRect()).y(); }); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); for (const shared_ptr& r : items) { assert(r); const bool highlight = !item_dragging_ && r->label_rect(rect).contains(mouse_point_); r->paint_label(painter, rect, highlight); } painter.end(); } void Header::contextMenuEvent(QContextMenuEvent *event) { const shared_ptr r = get_mouse_over_item(mouse_point_); if (!r) return; QMenu *menu = r->create_header_context_menu(this); if (!menu) menu = new QMenu(this); const vector< shared_ptr > items( view_.list_by_type()); if (count_if(items.begin(), items.end(), item_selected) > 1) { menu->addSeparator(); QAction *const group = new QAction(tr("Group"), this); QList shortcuts; shortcuts.append(QKeySequence(Qt::ControlModifier | Qt::Key_G)); group->setShortcuts(shortcuts); connect(group, SIGNAL(triggered()), this, SLOT(on_group())); menu->addAction(group); } menu->popup(event->globalPos()); } void Header::keyPressEvent(QKeyEvent *event) { assert(event); MarginWidget::keyPressEvent(event); if (event->key() == Qt::Key_G && event->modifiers() == Qt::ControlModifier) on_group(); else if (event->key() == Qt::Key_U && event->modifiers() == Qt::ControlModifier) on_ungroup(); } void Header::on_group() { const vector< shared_ptr > items( view_.list_by_type()); vector< shared_ptr > selected_items( make_filter_iterator(item_selected, items.begin(), items.end()), make_filter_iterator(item_selected, items.end(), items.end())); stable_sort(selected_items.begin(), selected_items.end(), [](const shared_ptr &a, const shared_ptr &b) { return a->visual_v_offset() < b->visual_v_offset(); }); shared_ptr group(new TraceGroup()); shared_ptr mouse_down_item( dynamic_pointer_cast(mouse_down_item_)); shared_ptr focus_item( mouse_down_item ? mouse_down_item : selected_items.front()); assert(focus_item); assert(focus_item->owner()); focus_item->owner()->add_child_item(group); // Set the group v_offset here before reparenting group->force_to_v_offset(focus_item->layout_v_offset() + focus_item->v_extents().first); for (size_t i = 0; i < selected_items.size(); i++) { const shared_ptr &r = selected_items[i]; assert(r->owner()); r->owner()->remove_child_item(r); group->add_child_item(r); // Put the items at 1-pixel offsets, so that restack will // stack them in the right order r->set_layout_v_offset(i); } } void Header::on_ungroup() { bool restart; do { restart = false; const vector< shared_ptr > groups( view_.list_by_type()); for (const shared_ptr& tg : groups) if (tg->selected()) { tg->ungroup(); restart = true; break; } } while (restart); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/tracetreeitemowner.hpp000600 001750 001750 00000005206 13365561737 022515 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEMOWNER_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEMOWNER_HPP #include "viewitemowner.hpp" #include "tracetreeitem.hpp" using std::pair; using std::shared_ptr; using std::vector; namespace pv { class Session; namespace views { namespace trace { class TraceTreeItem; class View; class TraceTreeItemOwner : public ViewItemOwner { public: /** * Returns the view of the owner. */ virtual View* view() = 0; /** * Returns the view of the owner. */ virtual const View* view() const = 0; virtual int owner_visual_v_offset() const = 0; /** * Returns the session of the owner. */ virtual Session& session() = 0; /** * Returns the session of the owner. */ virtual const Session& session() const = 0; /** * Returns the number of nested parents that this row item owner has. */ virtual unsigned int depth() const = 0; /** * Returns a list of row items owned by this object. */ virtual const item_list& child_items() const; /** * Returns a list of row items owned by this object. */ vector< shared_ptr > trace_tree_child_items() const; /** * Clears the list of child items. */ void clear_child_items(); /** * Adds a child item to this object. */ void add_child_item(shared_ptr item); /** * Removes a child item from this object. */ void remove_child_item(shared_ptr item); virtual void restack_items(); /** * Computes the vertical extents of the contents of this row item owner. * @return A pair containing the minimum and maximum y-values. */ pair v_extents() const; public: virtual void row_item_appearance_changed(bool label, bool content) = 0; virtual void extents_changed(bool horz, bool vert) = 0; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEMOWNER_HPP pulseview-0.4.1/pv/views/trace/marginwidget.hpp000600 001750 001750 00000003572 13365561737 021272 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_MARGINWIDGET_HPP #define PULSEVIEW_PV_MARGINWIDGET_HPP #include #include #include "viewwidget.hpp" using std::shared_ptr; namespace pv { namespace views { namespace trace { class ViewItem; class MarginWidget : public ViewWidget { Q_OBJECT public: MarginWidget(View &parent); /** * The extended area that the margin widget would like to be sized to. * @remarks This area is the area specified by sizeHint, extended by * the area to overlap the viewport. */ virtual QSize extended_size_hint() const = 0; protected: /** * Indicates the event an a view item has been clicked. * @param item the view item that has been clicked. */ virtual void item_clicked(const shared_ptr &item); /** * Shows the popup of a the specified @c ViewItem . * @param item The item to show the popup for. */ void show_popup(const shared_ptr &item); protected: virtual void contextMenuEvent(QContextMenuEvent *event); virtual void keyPressEvent(QKeyEvent *event); }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_MARGINWIDGET_HPP pulseview-0.4.1/pv/views/trace/timeitem.hpp000600 001750 001750 00000003274 13365561737 020425 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_TIMEITEM_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_TIMEITEM_HPP #include "viewitem.hpp" namespace pv { namespace views { namespace trace { class View; /** * The TimeItem class represents items on the @ref Ruler. It is generic in * nature, not making assumptions about the kind of item shown. */ class TimeItem : public ViewItem { Q_OBJECT protected: /** * Constructor. * @param view A reference to the view that owns this marker. */ TimeItem(View &view); public: /** * Sets the time of the marker. */ virtual void set_time(const pv::util::Timestamp& time) = 0; virtual float get_x() const = 0; /** * Drags the item to a delta relative to the drag point. * @param delta the offset from the drag point. */ void drag_by(const QPoint &delta); protected: View &view_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TIMEITEM_HPP pulseview-0.4.1/pv/views/trace/logicsignal.cpp000600 001750 001750 00000047213 13365561737 021077 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include "logicsignal.hpp" #include "view.hpp" #include #include #include #include #include #include #include #include using std::deque; using std::max; using std::make_pair; using std::min; using std::none_of; using std::out_of_range; using std::pair; using std::shared_ptr; using std::vector; using sigrok::ConfigKey; using sigrok::Capability; using sigrok::Trigger; using sigrok::TriggerMatch; using sigrok::TriggerMatchType; using pv::data::LogicSegment; namespace pv { namespace views { namespace trace { const float LogicSignal::Oversampling = 2.0f; const QColor LogicSignal::EdgeColor(0x80, 0x80, 0x80); const QColor LogicSignal::HighColor(0x00, 0xC0, 0x00); const QColor LogicSignal::LowColor(0xC0, 0x00, 0x00); const QColor LogicSignal::SamplingPointColor(0x77, 0x77, 0x77); const QColor LogicSignal::SignalColors[10] = { QColor(0x16, 0x19, 0x1A), // Black QColor(0x8F, 0x52, 0x02), // Brown QColor(0xCC, 0x00, 0x00), // Red QColor(0xF5, 0x79, 0x00), // Orange QColor(0xED, 0xD4, 0x00), // Yellow QColor(0x73, 0xD2, 0x16), // Green QColor(0x34, 0x65, 0xA4), // Blue QColor(0x75, 0x50, 0x7B), // Violet QColor(0x88, 0x8A, 0x85), // Grey QColor(0xEE, 0xEE, 0xEC), // White }; QColor LogicSignal::TriggerMarkerBackgroundColor = QColor(0xED, 0xD4, 0x00); const int LogicSignal::TriggerMarkerPadding = 2; const char* LogicSignal::TriggerMarkerIcons[8] = { nullptr, ":/icons/trigger-marker-low.svg", ":/icons/trigger-marker-high.svg", ":/icons/trigger-marker-rising.svg", ":/icons/trigger-marker-falling.svg", ":/icons/trigger-marker-change.svg", nullptr, nullptr }; QCache LogicSignal::icon_cache_; QCache LogicSignal::pixmap_cache_; LogicSignal::LogicSignal( pv::Session &session, shared_ptr device, shared_ptr base) : Signal(session, base), device_(device), trigger_types_(get_trigger_types()), trigger_none_(nullptr), trigger_rising_(nullptr), trigger_high_(nullptr), trigger_falling_(nullptr), trigger_low_(nullptr), trigger_change_(nullptr) { shared_ptr trigger; base_->set_color(SignalColors[base->index() % countof(SignalColors)]); GlobalSettings settings; signal_height_ = settings.value(GlobalSettings::Key_View_DefaultLogicHeight).toInt(); show_sampling_points_ = settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool(); fill_high_areas_ = settings.value(GlobalSettings::Key_View_FillSignalHighAreas).toBool(); high_fill_color_ = QColor::fromRgba(settings.value( GlobalSettings::Key_View_FillSignalHighAreaColor).value()); /* Populate this channel's trigger setting with whatever we * find in the current session trigger, if anything. */ trigger_match_ = nullptr; if ((trigger = session_.session()->trigger())) for (auto stage : trigger->stages()) for (auto match : stage->matches()) if (match->channel() == base_->channel()) trigger_match_ = match->type(); } shared_ptr LogicSignal::data() const { return base_->logic_data(); } shared_ptr LogicSignal::logic_data() const { return base_->logic_data(); } void LogicSignal::save_settings(QSettings &settings) const { settings.setValue("trace_height", signal_height_); } void LogicSignal::restore_settings(QSettings &settings) { if (settings.contains("trace_height")) { const int old_height = signal_height_; signal_height_ = settings.value("trace_height").toInt(); if ((signal_height_ != old_height) && owner_) { // Call order is important, otherwise the lazy event handler won't work owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } } } pair LogicSignal::v_extents() const { const int signal_margin = QFontMetrics(QApplication::font()).height() / 2; return make_pair(-signal_height_ - signal_margin, signal_margin); } void LogicSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp) { QLineF *line; vector< pair > edges; assert(base_); assert(owner_); const int y = get_visual_y(); if (!base_->enabled()) return; const float low_offset = y + 0.5f; const float high_offset = low_offset - signal_height_; 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; const double pixels_offset = pp.pixels_offset(); const pv::util::Timestamp& start_time = segment->start_time(); const int64_t last_sample = (int64_t)segment->get_sample_count() - 1; const double samples_per_pixel = samplerate * pp.scale(); const double pixels_per_sample = 1 / samples_per_pixel; 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(floor(start).convert_to(), (int64_t)0), last_sample); const uint64_t end_sample = min(max(ceil(end).convert_to(), (int64_t)0), last_sample); segment->get_subsampled_edges(edges, start_sample, end_sample, samples_per_pixel / Oversampling, base_->index()); assert(edges.size() >= 2); const float first_sample_x = pp.left() + (edges.front().first / samples_per_pixel - pixels_offset); const float last_sample_x = pp.left() + (edges.back().first / samples_per_pixel - pixels_offset); // Check whether we need to paint the sampling points const bool show_sampling_points = show_sampling_points_ && (samples_per_pixel < 0.25); vector sampling_points; float sampling_point_x = first_sample_x; int64_t sampling_point_sample = start_sample; const int w = 2; if (show_sampling_points) sampling_points.reserve(end_sample - start_sample + 1); vector high_rects; float rising_edge_x; bool rising_edge_seen = false; // Paint the edges const unsigned int edge_count = edges.size() - 2; QLineF *const edge_lines = new QLineF[edge_count]; line = edge_lines; if (edges.front().second) { // Beginning of trace is high rising_edge_x = first_sample_x; rising_edge_seen = true; } for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) { // Note: multiple edges occupying a single pixel are represented by an edge // with undefined logic level. This means that only the first falling edge // after a rising edge corresponds to said rising edge - and vice versa. If // more edges with the same logic level follow, they denote multiple edges. const float x = pp.left() + ((*i).first / samples_per_pixel - pixels_offset); *line++ = QLineF(x, high_offset, x, low_offset); if (fill_high_areas_) { // Any edge terminates a high area if (rising_edge_seen) { const int width = x - rising_edge_x; if (width > 0) high_rects.emplace_back(rising_edge_x, high_offset, width, signal_height_); rising_edge_seen = false; } // Only rising edges start high areas if ((*i).second) { rising_edge_x = x; rising_edge_seen = true; } } if (show_sampling_points) while (sampling_point_sample < (*i).first) { const float y = (*i).second ? low_offset : high_offset; sampling_points.emplace_back( QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); sampling_point_sample++; sampling_point_x += pixels_per_sample; }; } // Calculate the sample points from the last edge to the end of the trace if (show_sampling_points) while ((uint64_t)sampling_point_sample <= end_sample) { // Signal changed after the last edge, so the level is inverted const float y = (edges.cend() - 1)->second ? high_offset : low_offset; sampling_points.emplace_back( QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); sampling_point_sample++; sampling_point_x += pixels_per_sample; }; if (fill_high_areas_) { // Add last high rectangle if the signal is still high at the end of the trace if (rising_edge_seen && (edges.cend() - 1)->second) high_rects.emplace_back(rising_edge_x, high_offset, last_sample_x - rising_edge_x, signal_height_); p.setPen(high_fill_color_); p.setBrush(high_fill_color_); p.drawRects((const QRectF*)(high_rects.data()), high_rects.size()); } p.setPen(EdgeColor); p.drawLines(edge_lines, edge_count); delete[] edge_lines; // Paint the caps const unsigned int max_cap_line_count = edges.size(); QLineF *const cap_lines = new QLineF[max_cap_line_count]; p.setPen(HighColor); paint_caps(p, cap_lines, edges, true, samples_per_pixel, pixels_offset, pp.left(), high_offset); p.setPen(LowColor); paint_caps(p, cap_lines, edges, false, samples_per_pixel, pixels_offset, pp.left(), low_offset); delete[] cap_lines; // Paint the sampling points if (show_sampling_points) { p.setPen(SamplingPointColor); p.drawRects(sampling_points.data(), sampling_points.size()); } } void LogicSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp) { if (base_->enabled()) { if (trigger_match_) { // Draw the trigger marker const int y = get_visual_y(); for (int32_t type_id : trigger_types_) { const TriggerMatchType *const type = TriggerMatchType::get(type_id); if (trigger_match_ != type || type_id < 0 || (size_t)type_id >= countof(TriggerMarkerIcons) || !TriggerMarkerIcons[type_id]) continue; const QPixmap *const pixmap = get_pixmap( TriggerMarkerIcons[type_id]); if (!pixmap) continue; const float pad = TriggerMarkerPadding - 0.5f; const QSize size = pixmap->size(); const QPoint point( pp.right() - size.width() - pad * 2, y - (signal_height_ + size.height()) / 2); p.setPen(QPen(TriggerMarkerBackgroundColor.darker())); p.setBrush(TriggerMarkerBackgroundColor); p.drawRoundedRect(QRectF(point, size).adjusted( -pad, -pad, pad, pad), pad, pad); p.drawPixmap(point, *pixmap); break; } } if (show_hover_marker_) paint_hover_marker(p); } } vector LogicSignal::get_nearest_level_changes(uint64_t sample_pos) { assert(base_); assert(owner_); 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 / Oversampling, base_->index()); if (edges.empty()) return vector(); return edges; } void LogicSignal::paint_caps(QPainter &p, QLineF *const lines, vector< pair > &edges, bool level, double samples_per_pixel, double pixels_offset, float x_offset, float y_offset) { QLineF *line = lines; for (auto i = edges.begin(); i != (edges.end() - 1); i++) if ((*i).second == level) { *line++ = QLineF( ((*i).first / samples_per_pixel - pixels_offset) + x_offset, y_offset, ((*(i+1)).first / samples_per_pixel - pixels_offset) + x_offset, y_offset); } p.drawLines(lines, line - lines); } shared_ptr LogicSignal::get_logic_segment_to_paint() const { shared_ptr segment; const deque< shared_ptr > &segments = base_->logic_data()->logic_segments(); if (!segments.empty()) { if (segment_display_mode_ == ShowLastSegmentOnly) { segment = segments.back(); } if ((segment_display_mode_ == ShowSingleSegmentOnly) || (segment_display_mode_ == ShowLastCompleteSegmentOnly)) { try { segment = segments.at(current_segment_); } catch (out_of_range&) { qDebug() << "Current logic segment out of range for signal" << base_->name() << ":" << current_segment_; } } } return segment; } void LogicSignal::init_trigger_actions(QWidget *parent) { trigger_none_ = new QAction(*get_icon(":/icons/trigger-none.svg"), tr("No trigger"), parent); trigger_none_->setCheckable(true); connect(trigger_none_, SIGNAL(triggered()), this, SLOT(on_trigger())); trigger_rising_ = new QAction(*get_icon(":/icons/trigger-rising.svg"), tr("Trigger on rising edge"), parent); trigger_rising_->setCheckable(true); connect(trigger_rising_, SIGNAL(triggered()), this, SLOT(on_trigger())); trigger_high_ = new QAction(*get_icon(":/icons/trigger-high.svg"), tr("Trigger on high level"), parent); trigger_high_->setCheckable(true); connect(trigger_high_, SIGNAL(triggered()), this, SLOT(on_trigger())); trigger_falling_ = new QAction(*get_icon(":/icons/trigger-falling.svg"), tr("Trigger on falling edge"), parent); trigger_falling_->setCheckable(true); connect(trigger_falling_, SIGNAL(triggered()), this, SLOT(on_trigger())); trigger_low_ = new QAction(*get_icon(":/icons/trigger-low.svg"), tr("Trigger on low level"), parent); trigger_low_->setCheckable(true); connect(trigger_low_, SIGNAL(triggered()), this, SLOT(on_trigger())); trigger_change_ = new QAction(*get_icon(":/icons/trigger-change.svg"), tr("Trigger on rising or falling edge"), parent); trigger_change_->setCheckable(true); connect(trigger_change_, SIGNAL(triggered()), this, SLOT(on_trigger())); } const vector LogicSignal::get_trigger_types() const { // We may not be associated with a device if (!device_) return vector(); const auto sr_dev = device_->device(); if (sr_dev->config_check(ConfigKey::TRIGGER_MATCH, Capability::LIST)) { const Glib::VariantContainerBase gvar = sr_dev->config_list(ConfigKey::TRIGGER_MATCH); vector ttypes; for (unsigned int i = 0; i < gvar.get_n_children(); i++) { Glib::VariantBase tmp_vb; gvar.get_child(tmp_vb, i); Glib::Variant tmp_v = Glib::VariantBase::cast_dynamic< Glib::Variant >(tmp_vb); ttypes.push_back(tmp_v.get()); } return ttypes; } else { return vector(); } } QAction* LogicSignal::action_from_trigger_type(const TriggerMatchType *type) { QAction *action; action = trigger_none_; if (type) { switch (type->id()) { case SR_TRIGGER_ZERO: action = trigger_low_; break; case SR_TRIGGER_ONE: action = trigger_high_; break; case SR_TRIGGER_RISING: action = trigger_rising_; break; case SR_TRIGGER_FALLING: action = trigger_falling_; break; case SR_TRIGGER_EDGE: action = trigger_change_; break; default: assert(false); } } return action; } const TriggerMatchType *LogicSignal::trigger_type_from_action(QAction *action) { if (action == trigger_low_) return TriggerMatchType::ZERO; else if (action == trigger_high_) return TriggerMatchType::ONE; else if (action == trigger_rising_) return TriggerMatchType::RISING; else if (action == trigger_falling_) return TriggerMatchType::FALLING; else if (action == trigger_change_) return TriggerMatchType::EDGE; else return nullptr; } void LogicSignal::populate_popup_form(QWidget *parent, QFormLayout *form) { Signal::populate_popup_form(parent, form); signal_height_sb_ = new QSpinBox(parent); signal_height_sb_->setRange(5, 1000); signal_height_sb_->setSingleStep(5); signal_height_sb_->setSuffix(tr(" pixels")); signal_height_sb_->setValue(signal_height_); connect(signal_height_sb_, SIGNAL(valueChanged(int)), this, SLOT(on_signal_height_changed(int))); form->addRow(tr("Trace height"), signal_height_sb_); // Trigger settings const vector trig_types = get_trigger_types(); if (!trig_types.empty()) { trigger_bar_ = new QToolBar(parent); init_trigger_actions(trigger_bar_); trigger_bar_->addAction(trigger_none_); trigger_none_->setChecked(!trigger_match_); for (auto type_id : trig_types) { const TriggerMatchType *const type = TriggerMatchType::get(type_id); QAction *const action = action_from_trigger_type(type); trigger_bar_->addAction(action); action->setChecked(trigger_match_ == type); } // Only allow triggers to be changed when we're stopped if (session_.get_capture_state() != Session::Stopped) for (QAction* action : trigger_bar_->findChildren()) // clazy:exclude=range-loop action->setEnabled(false); form->addRow(tr("Trigger"), trigger_bar_); } } void LogicSignal::modify_trigger() { auto trigger = session_.session()->trigger(); auto new_trigger = session_.device_manager().context()->create_trigger("pulseview"); if (trigger) { for (auto stage : trigger->stages()) { const auto &matches = stage->matches(); if (none_of(matches.begin(), matches.end(), [&](shared_ptr match) { return match->channel() != base_->channel(); })) continue; auto new_stage = new_trigger->add_stage(); for (auto match : stage->matches()) { if (match->channel() == base_->channel()) continue; new_stage->add_match(match->channel(), match->type()); } } } if (trigger_match_) { // Until we can let the user decide how to group trigger matches // into stages, put all of the matches into a single stage -- // most devices only support a single trigger stage. if (new_trigger->stages().empty()) new_trigger->add_stage(); new_trigger->stages().back()->add_match(base_->channel(), trigger_match_); } session_.session()->set_trigger( new_trigger->stages().empty() ? nullptr : new_trigger); if (owner_) owner_->row_item_appearance_changed(false, true); } const QIcon* LogicSignal::get_icon(const char *path) { if (!icon_cache_.contains(path)) { const QIcon *icon = new QIcon(path); icon_cache_.insert(path, icon); } return icon_cache_.take(path); } const QPixmap* LogicSignal::get_pixmap(const char *path) { if (!pixmap_cache_.contains(path)) { const QPixmap *pixmap = new QPixmap(path); pixmap_cache_.insert(path, pixmap); } return pixmap_cache_.take(path); } void LogicSignal::on_setting_changed(const QString &key, const QVariant &value) { Signal::on_setting_changed(key, value); if (key == GlobalSettings::Key_View_ShowSamplingPoints) show_sampling_points_ = value.toBool(); if (key == GlobalSettings::Key_View_FillSignalHighAreas) fill_high_areas_ = value.toBool(); if (key == GlobalSettings::Key_View_FillSignalHighAreaColor) high_fill_color_ = QColor::fromRgba(value.value()); } void LogicSignal::on_trigger() { QAction *action; action_from_trigger_type(trigger_match_)->setChecked(false); action = (QAction *)sender(); action->setChecked(true); trigger_match_ = trigger_type_from_action(action); modify_trigger(); } void LogicSignal::on_signal_height_changed(int height) { signal_height_ = height; if (owner_) { // Call order is important, otherwise the lazy event handler won't work owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/tracegroup.hpp000600 001750 001750 00000006352 13365561737 020763 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEGROUP_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEGROUP_HPP #include "tracetreeitem.hpp" #include "tracetreeitemowner.hpp" using std::pair; namespace pv { namespace views { namespace trace { class TraceGroup : public TraceTreeItem, public TraceTreeItemOwner { Q_OBJECT private: static const int Padding; static const int Width; static const int LineThickness; static const QColor LineColor; public: /** * Virtual destructor */ virtual ~TraceGroup(); /** * Returns true if the item is visible and enabled. */ bool enabled() const; /** * Returns the session of the onwer. */ pv::Session& session(); /** * Returns the session of the onwer. */ const pv::Session& session() const; /** * Returns the view of the owner. */ virtual View* view(); /** * Returns the view of the owner. */ virtual const View* view() const; /** * Computes the vertical extents of the contents of this row item. * @return A pair containing the minimum and maximum y-values. */ pair v_extents() const; /** * Paints the signal label. * @param p the QPainter to paint into. * @param right the x-coordinate of the right edge of the header * area. * @param hover true if the label is being hovered over by the mouse. */ void paint_label(QPainter &p, const QRect &rect, bool hover); /** * Computes the outline rectangle of a label. * @param rect the rectangle of the header area. * @return Returns the rectangle of the signal label. */ QRectF label_rect(const QRectF &rect) const; /** * Determines if a point is in the header label rect. * @param left the x-coordinate of the left edge of the header * area. * @param right the x-coordinate of the right edge of the header * area. * @param point the point to test. */ bool pt_in_label_rect(int left, int right, const QPoint &point); QMenu* create_header_context_menu(QWidget *parent); pv::widgets::Popup* create_popup(QWidget *parent); /** * Returns the total vertical offset of this trace and all it's owners */ int owner_visual_v_offset() const; /** * Returns the number of nested parents that this row item owner has. */ unsigned int depth() const; void ungroup(); public: void row_item_appearance_changed(bool label, bool content); void extents_changed(bool horz, bool vert); private Q_SLOTS: void on_ungroup(); }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACEGROUP_HPP pulseview-0.4.1/pv/views/trace/viewitemowner.hpp000600 001750 001750 00000004533 13365561737 021513 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMOWNER_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMOWNER_HPP #include #include #include "viewitemiterator.hpp" using std::dynamic_pointer_cast; using std::shared_ptr; using std::vector; namespace pv { class Session; namespace views { namespace trace { class ViewItem; class View; class ViewItemOwner { public: typedef vector< shared_ptr > item_list; typedef ViewItemIterator iterator; typedef ViewItemIterator const_iterator; public: /** * Returns a list of row items owned by this object. */ virtual const item_list& child_items() const; /** * Returns a depth-first iterator at the beginning of the child ViewItem * tree. */ iterator begin(); /** * Returns a depth-first iterator at the end of the child ViewItem tree. */ iterator end(); /** * Returns a constant depth-first iterator at the beginning of the * child ViewItem tree. */ const_iterator begin() const; /** * Returns a constant depth-first iterator at the end of the child * ViewItem tree. */ const_iterator end() const; /** * Creates a list of descendant signals filtered by type. */ template vector< shared_ptr > list_by_type() { vector< shared_ptr > items; for (const auto &r : *this) { shared_ptr p = dynamic_pointer_cast(r); if (p) items.push_back(p); } return items; } protected: item_list items_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMOWNER_HPP pulseview-0.4.1/pv/views/trace/triggermarker.hpp000600 001750 001750 00000004526 13365561737 021456 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_TRIGGER_MARKER_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRIGGER_MARKER_HPP #include "timeitem.hpp" #include namespace pv { namespace views { namespace trace { /** * The TriggerMarker class is used to show to the user at what point in time * a trigger occured. It is not editable by the user. */ class TriggerMarker : public TimeItem { Q_OBJECT public: static const QColor Color; public: /** * Constructor. * @param view A reference to the view that owns this marker. * @param time The time to set the marker to. */ TriggerMarker(View &view, const pv::util::Timestamp& time); /** * Copy constructor. */ TriggerMarker(const TriggerMarker &marker); /** * Returns true if the item is visible and enabled. */ bool enabled() const override; /** Returns true if the item may be dragged/moved. */ bool is_draggable(QPoint pos) const override; /** * Sets the time of the marker. */ void set_time(const pv::util::Timestamp& time) override; float get_x() const override; /** * Gets the arrow-tip point of the time marker. * @param rect the rectangle of the ruler area. */ QPoint drag_point(const QRect &rect) const override; /** * Paints the foreground layer of the item with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with. */ void paint_fore(QPainter &p, ViewItemPaintParams &pp) override; private: pv::util::Timestamp time_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRIGGER_MARKER_HPP pulseview-0.4.1/pv/views/trace/viewitemiterator.hpp000600 001750 001750 00000006646 13365561737 022221 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMITERATOR_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMITERATOR_HPP #include #include #include #include #include #include #include #include using std::dynamic_pointer_cast; using std::forward_iterator_tag; using std::shared_ptr; using std::stack; namespace pv { namespace views { namespace trace { template class ViewItemIterator { public: typedef typename Owner::item_list::const_iterator child_iterator; typedef shared_ptr value_type; typedef ptrdiff_t difference_type; typedef value_type pointer; typedef const value_type& reference; typedef forward_iterator_tag iterator_category; public: ViewItemIterator(Owner *owner) : owner_stack_({owner}) {} ViewItemIterator(Owner *owner, child_iterator iter) : owner_stack_({owner}) { assert(owner); if (iter != owner->child_items().end()) iter_stack_.push(iter); } ViewItemIterator(const ViewItemIterator &o) : owner_stack_(o.owner_stack_), iter_stack_(o.iter_stack_) {} reference operator*() const { return *iter_stack_.top(); } reference operator->() const { return *this; } ViewItemIterator& operator++() { assert(!owner_stack_.empty()); assert(!iter_stack_.empty()); shared_ptr owner(dynamic_pointer_cast( *iter_stack_.top())); if (owner && !owner->child_items().empty()) { owner_stack_.push(owner.get()); iter_stack_.push(owner->child_items().begin()); } else { while (!iter_stack_.empty() && (++iter_stack_.top()) == owner_stack_.top()->child_items().end()) { owner_stack_.pop(); iter_stack_.pop(); } } return *this; } ViewItemIterator operator++(int) { ViewItemIterator pre = *this; ++*this; return pre; } bool operator==(const ViewItemIterator &o) const { return (iter_stack_.empty() && o.iter_stack_.empty()) || ( iter_stack_.size() == o.iter_stack_.size() && owner_stack_.top() == o.owner_stack_.top() && iter_stack_.top() == o.iter_stack_.top()); } bool operator!=(const ViewItemIterator &o) const { return !((const ViewItemIterator&)*this == o); } void swap(ViewItemIterator& other) { swap(owner_stack_, other.owner_stack_); swap(iter_stack_, other.iter_stack_); } private: stack owner_stack_; stack iter_stack_; }; template void swap(ViewItemIterator& a, ViewItemIterator& b) { a.swap(b); } } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWITEMITERATOR_HPP pulseview-0.4.1/pv/views/trace/view.hpp000600 001750 001750 00000032372 13365561737 017563 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEW_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEW_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cursorpair.hpp" #include "flag.hpp" #include "trace.hpp" #include "tracetreeitemowner.hpp" using std::list; using std::unordered_map; using std::unordered_set; using std::set; using std::shared_ptr; using std::vector; namespace sigrok { class ChannelGroup; } namespace pv { class Session; namespace data { class Logic; } namespace views { namespace trace { class DecodeTrace; class Header; class Ruler; class Signal; class Viewport; class TriggerMarker; class CustomScrollArea : public QAbstractScrollArea { Q_OBJECT public: CustomScrollArea(QWidget *parent = nullptr); bool viewportEvent(QEvent *event); }; class View : public ViewBase, public TraceTreeItemOwner, public GlobalSettingsInterface { Q_OBJECT private: enum StickyEvents { TraceTreeItemHExtentsChanged = 1, TraceTreeItemVExtentsChanged = 2 }; private: static const pv::util::Timestamp MaxScale; static const pv::util::Timestamp MinScale; static const int MaxScrollValue; static const int ScaleUnits[3]; public: explicit View(Session &session, bool is_main_view=false, QWidget *parent = nullptr); ~View(); /** * Resets the view to its default state after construction. It does however * not reset the signal bases or any other connections with the session. */ virtual void reset_view_state(); Session& session(); const Session& session() const; /** * Returns the signals contained in this view. */ unordered_set< shared_ptr > signals() const; virtual void clear_signals(); void add_signal(const shared_ptr signal); #ifdef ENABLE_DECODE virtual void clear_decode_signals(); virtual void add_decode_signal(shared_ptr signal); virtual void remove_decode_signal(shared_ptr signal); #endif shared_ptr get_signal_under_mouse_cursor() const; /** * Returns the view of the owner. */ virtual View* view(); /** * Returns the view of the owner. */ virtual const View* view() const; Viewport* viewport(); const Viewport* viewport() const; const Ruler* ruler() const; virtual void save_settings(QSettings &settings) const; virtual void restore_settings(QSettings &settings); /** * Gets a list of time markers. */ vector< shared_ptr > time_items() const; /** * Returns the view time scale in seconds per pixel. */ double scale() const; /** * Returns the internal view version of the time offset of the left edge * of the view in seconds. */ const pv::util::Timestamp& offset() const; /** * Returns the ruler version of the time offset of the left edge * of the view in seconds. */ const pv::util::Timestamp& ruler_offset() const; void set_zero_position(const pv::util::Timestamp& position); void reset_zero_position(); /** * Returns the vertical scroll offset. */ int owner_visual_v_offset() const; /** * Sets the visual v-offset. */ void set_v_offset(int offset); /** * Returns the SI prefix to apply to the graticule time markings. */ pv::util::SIPrefix tick_prefix() const; /** * Returns the number of fractional digits shown for the time markings. */ unsigned int tick_precision() const; /** * Returns period of the graticule time markings. */ const pv::util::Timestamp& tick_period() const; /** * Returns number of minor division ticks per time marking. */ unsigned int minor_tick_count() const; /** * Returns the unit of time currently used. */ util::TimeUnit time_unit() const; /** * Returns the number of nested parents that this row item owner has. */ unsigned int depth() const; /** * Returns the currently displayed segment, starting at 0. */ uint32_t current_segment() const; /** * Returns whether the currently shown segment can be influenced * (selected) or not. */ bool segment_is_selectable() const; Trace::SegmentDisplayMode segment_display_mode() const; void set_segment_display_mode(Trace::SegmentDisplayMode mode); void zoom(double steps); void zoom(double steps, int offset); void zoom_fit(bool gui_state); /** * Sets the scale and 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, const pv::util::Timestamp& offset); set< shared_ptr > get_visible_data() const; pair get_time_extents() const; /** * Enables or disables colored trace backgrounds. If they're not * colored then they will use alternating colors. */ void enable_colored_bg(bool state); /** * Returns true if the trace background should be drawn with a colored background. */ bool colored_bg() const; /** * Enable or disable showing sampling points. */ void enable_show_sampling_points(bool state); /** * Enable or disable showing the analog minor grid. */ void enable_show_analog_minor_grid(bool state); /** * Returns true if cursors are displayed. false otherwise. */ bool cursors_shown() const; /** * Shows or hides the cursors. */ void show_cursors(bool show = true); /** * Moves the cursors to a convenient position in the view. */ void centre_cursors(); /** * Returns a reference to the pair of cursors. */ shared_ptr cursors() const; /** * Adds a new flag at a specified time. */ void add_flag(const pv::util::Timestamp& time); /** * Removes a flag from the list. */ void remove_flag(shared_ptr flag); /** * Gets the list of flags. */ vector< shared_ptr > flags() const; const QPoint& hover_point() const; const QWidget* hover_widget() 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); void restack_all_trace_tree_items(); int header_width() const; void on_setting_changed(const QString &key, const QVariant &value); Q_SIGNALS: void hover_point_changed(const QWidget* widget, const QPoint &hp); void selection_changed(); /// Emitted when the offset changed. void offset_changed(); /// Emitted when the scale changed. void scale_changed(); void sticky_scrolling_changed(bool state); void always_zoom_to_fit_changed(bool state); /// Emitted when the tick_prefix changed. void tick_prefix_changed(); /// Emitted when the tick_precision changed. void tick_precision_changed(); /// Emitted when the tick_period changed. void tick_period_changed(); /// Emitted when the time_unit changed. void time_unit_changed(); /// Emitted when the currently selected segment changed void segment_changed(int segment_id); /// Emitted when the multi-segment display mode changed /// @param mode is a value of Trace::SegmentDisplayMode void segment_display_mode_changed(int mode, bool segment_selectable); /// Emitted when the cursors are shown/hidden void cursor_state_changed(bool show); public Q_SLOTS: void trigger_event(int segment_id, util::Timestamp location); private: void get_scroll_layout(double &length, pv::util::Timestamp &offset) const; /** * Simultaneously sets the zoom and offset. * @param scale The scale to set the view to in seconds per pixel. This * value is clamped between MinScale and MaxScale. * @param offset The offset of the left edge of the view in seconds. */ void set_zoom(double scale, int offset); /** * Find a tick spacing and number formatting that does not cause * the values to collide. */ void calculate_tick_spacing(); void adjust_top_margin(); void update_scroll(); void reset_scroll(); void set_scroll_default(); void determine_if_header_was_shrunk(); void resize_header_to_fit(); void update_layout(); TraceTreeItemOwner* find_prevalent_trace_group( const shared_ptr &group, const unordered_map, shared_ptr > &signal_map); static vector< shared_ptr > extract_new_traces_for_channels( const vector< shared_ptr > &channels, const unordered_map, shared_ptr > &signal_map, set< shared_ptr > &add_list); void determine_time_unit(); bool eventFilter(QObject *object, QEvent *event); virtual void contextMenuEvent(QContextMenuEvent *event); void resizeEvent(QResizeEvent *event); void update_hover_point(); public: void row_item_appearance_changed(bool label, bool content); void time_item_appearance_changed(bool label, bool content); void extents_changed(bool horz, bool vert); private Q_SLOTS: void on_signal_name_changed(); void on_splitter_moved(); void h_scroll_value_changed(int value); void v_scroll_value_changed(); void signals_changed(); void capture_state_updated(int state); void on_new_segment(int new_segment_id); void on_segment_completed(int new_segment_id); void on_segment_changed(int segment); void on_settingViewTriggerIsZeroTime_changed(const QVariant new_value); virtual void perform_delayed_view_update(); void process_sticky_events(); /** * Sets the 'offset_' and ruler_offset_ members and emits the 'offset_changed' * signal if needed. */ void set_offset(const pv::util::Timestamp& offset, bool force_update = false); /** * Sets the 'scale_' member and emits the 'scale_changed' * signal if needed. */ void set_scale(double scale); /** * Sets the 'tick_prefix_' member and emits the 'tick_prefix_changed' * signal if needed. */ void set_tick_prefix(pv::util::SIPrefix tick_prefix); /** * Sets the 'tick_precision_' member and emits the 'tick_precision_changed' * signal if needed. */ void set_tick_precision(unsigned tick_precision); /** * Sets the 'tick_period_' member and emits the 'tick_period_changed' * signal if needed. */ void set_tick_period(const pv::util::Timestamp& tick_period); /** * Sets the 'time_unit' member and emits the 'time_unit_changed' * signal if needed. */ void set_time_unit(pv::util::TimeUnit time_unit); /** * Sets the current segment with the first segment starting at 0. */ void set_current_segment(uint32_t segment_id); private: CustomScrollArea *scrollarea_; Viewport *viewport_; Ruler *ruler_; Header *header_; QSplitter *splitter_; unordered_set< shared_ptr > signals_; #ifdef ENABLE_DECODE vector< shared_ptr > decode_traces_; #endif Trace::SegmentDisplayMode segment_display_mode_; /// Signals whether the user can change the currently shown segment. bool segment_selectable_; /// The view time scale in seconds per pixel. double scale_; /// The internal view version of the time offset in seconds. pv::util::Timestamp offset_; /// The ruler version of the time offset in seconds. pv::util::Timestamp ruler_offset_; bool updating_scroll_; bool settings_restored_; bool header_was_shrunk_; bool sticky_scrolling_; bool colored_bg_; bool always_zoom_to_fit_; pv::util::Timestamp tick_period_; pv::util::SIPrefix tick_prefix_; unsigned int minor_tick_count_; unsigned int tick_precision_; util::TimeUnit time_unit_; bool show_cursors_; shared_ptr cursors_; list< shared_ptr > flags_; char next_flag_text_; vector< shared_ptr > trigger_markers_; QWidget* hover_widget_; QPoint hover_point_; shared_ptr signal_under_mouse_cursor_; uint16_t snap_distance_; unsigned int sticky_events_; QTimer lazy_event_handler_; // This is true when the defaults couldn't be set due to insufficient info bool scroll_needs_defaults_; // A nonzero value indicates the v offset to restore. See View::resizeEvent() int saved_v_offset_; // These are used to determine whether the view was altered after acq started double scale_at_acq_start_; pv::util::Timestamp offset_at_acq_start_; // Used to suppress performing a "zoom to fit" when the session stops. This // is needed when the view's settings are restored before acquisition ends. // In that case we want to keep the restored settings, not have a "zoom to fit" // mess them up. bool suppress_zoom_to_fit_after_acq_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEW_HPP pulseview-0.4.1/pv/views/trace/tracetreeitem.hpp000600 001750 001750 00000006107 13365561737 021443 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEM_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEM_HPP #include #include #include "rowitem.hpp" using std::enable_shared_from_this; using std::pair; namespace pv { namespace views { namespace trace { class TraceTreeItemOwner; class TraceTreeItem : public RowItem, public enable_shared_from_this { Q_OBJECT Q_PROPERTY(int visual_v_offset READ visual_v_offset WRITE set_visual_v_offset) public: /** * Constructor. */ TraceTreeItem(); /** * Gets the owner of this item in the view item hierachy. */ TraceTreeItemOwner* owner() const; /** * Selects or deselects the signal. */ void select(bool select = true); /** * Gets the vertical layout offset of this signal. */ int layout_v_offset() const; /** * Sets the vertical layout offset of this signal. */ void set_layout_v_offset(int v_offset); /** * Gets the vertical visual offset of this signal. */ int visual_v_offset() const; /** * Sets the vertical visual offset of this signal. */ void set_visual_v_offset(int v_offset); /** * Sets the visual and layout offset of this signal. */ void force_to_v_offset(int v_offset); /** * Begins an animation that will animate the visual offset toward * the layout offset. */ void animate_to_layout_v_offset(); /** * Sets the owner this trace in the view trace hierachy. * @param The new owner of the trace. */ void set_owner(TraceTreeItemOwner *owner); /** * Gets the visual y-offset of the axis. */ int get_visual_y() const; /** * Drags the item to a delta relative to the drag point. * @param delta the offset from the drag point. */ void drag_by(const QPoint &delta); /** * Gets the arrow-tip point of the row item marker. * @param rect the rectangle of the header area. */ QPoint drag_point(const QRect &rect) const; /** * Computes the vertical extents of the contents of this row item. * @return A pair containing the minimum and maximum y-values. */ virtual pair v_extents() const = 0; protected: TraceTreeItemOwner *owner_; int layout_v_offset_; int visual_v_offset_; private: QPropertyAnimation v_offset_animation_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TRACETREEITEM_HPP pulseview-0.4.1/pv/views/trace/ruler.cpp000600 001750 001750 00000023637 13365561737 017741 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include "ruler.hpp" #include "view.hpp" using namespace Qt; using std::function; using std::max; using std::min; using std::shared_ptr; using std::vector; namespace pv { namespace views { namespace trace { const float Ruler::RulerHeight = 2.5f; // x Text Height const float Ruler::HoverArrowSize = 0.5f; // x Text Height Ruler::Ruler(View &parent) : MarginWidget(parent) { setMouseTracking(true); connect(&view_, SIGNAL(hover_point_changed(const QWidget*, QPoint)), this, SLOT(on_hover_point_changed(const QWidget*, QPoint))); connect(&view_, SIGNAL(offset_changed()), this, SLOT(invalidate_tick_position_cache())); connect(&view_, SIGNAL(scale_changed()), this, SLOT(invalidate_tick_position_cache())); connect(&view_, SIGNAL(tick_prefix_changed()), this, SLOT(invalidate_tick_position_cache())); connect(&view_, SIGNAL(tick_precision_changed()), this, SLOT(invalidate_tick_position_cache())); connect(&view_, SIGNAL(tick_period_changed()), this, SLOT(invalidate_tick_position_cache())); connect(&view_, SIGNAL(time_unit_changed()), this, SLOT(invalidate_tick_position_cache())); } QSize Ruler::sizeHint() const { const int text_height = calculate_text_height(); return QSize(0, RulerHeight * text_height); } QSize Ruler::extended_size_hint() const { QRectF max_rect; vector< shared_ptr > items(view_.time_items()); for (auto &i : items) max_rect = max_rect.united(i->label_rect(QRect())); return QSize(0, sizeHint().height() - max_rect.top() / 2 + ViewItem::HighlightRadius); } QString Ruler::format_time_with_distance( const pv::util::Timestamp& distance, const pv::util::Timestamp& t, pv::util::SIPrefix prefix, pv::util::TimeUnit unit, unsigned precision, bool sign) { const unsigned limit = 60; if (t.is_zero()) return "0"; // If we have to use samples then we have no alternative formats if (unit == pv::util::TimeUnit::Samples) return pv::util::format_time_si_adjusted(t, prefix, precision, "sa", sign); // View zoomed way out -> low precision (0), big distance (>=60s) // -> DD:HH:MM if ((precision == 0) && (distance >= limit)) return pv::util::format_time_minutes(t, 0, sign); // View in "normal" range -> medium precision, medium step size // -> HH:MM:SS.mmm... or xxxx (si unit) if less than limit seconds // View zoomed way in -> high precision (>3), low step size (<1s) // -> HH:MM:SS.mmm... or xxxx (si unit) if less than limit seconds if (abs(t) < limit) return pv::util::format_time_si_adjusted(t, prefix, precision, "s", sign); else return pv::util::format_time_minutes(t, precision, sign); } pv::util::Timestamp Ruler::get_time_from_x_pos(uint32_t x) const { return view_.ruler_offset() + ((double)x + 0.5) * view_.scale(); } void Ruler::contextMenuEvent(QContextMenuEvent *event) { MarginWidget::contextMenuEvent(event); // Don't show a context menu if the MarginWidget found a widget that shows one if (event->isAccepted()) return; context_menu_x_pos_ = event->pos().x(); QMenu *const menu = new QMenu(this); QAction *const create_marker = new QAction(tr("Create marker here"), this); connect(create_marker, SIGNAL(triggered()), this, SLOT(on_createMarker())); menu->addAction(create_marker); QAction *const set_zero_position = new QAction(tr("Set as zero point"), this); connect(set_zero_position, SIGNAL(triggered()), this, SLOT(on_setZeroPosition())); menu->addAction(set_zero_position); QAction *const toggle_hover_marker = new QAction(this); connect(toggle_hover_marker, SIGNAL(triggered()), this, SLOT(on_toggleHoverMarker())); menu->addAction(toggle_hover_marker); GlobalSettings settings; const bool hover_marker_shown = settings.value(GlobalSettings::Key_View_ShowHoverMarker).toBool(); toggle_hover_marker->setText(hover_marker_shown ? tr("Disable mouse hover marker") : tr("Enable mouse hover marker")); event->setAccepted(true); menu->popup(event->globalPos()); } void Ruler::resizeEvent(QResizeEvent*) { // the tick calculation depends on the width of this widget invalidate_tick_position_cache(); } vector< shared_ptr > Ruler::items() { const vector< shared_ptr > time_items(view_.time_items()); return vector< shared_ptr >( time_items.begin(), time_items.end()); } shared_ptr Ruler::get_mouse_over_item(const QPoint &pt) { const vector< shared_ptr > items(view_.time_items()); for (auto i = items.rbegin(); i != items.rend(); i++) if ((*i)->enabled() && (*i)->label_rect(rect()).contains(pt)) return *i; return nullptr; } void Ruler::mouseDoubleClickEvent(QMouseEvent *event) { view_.add_flag(get_time_from_x_pos(event->x())); } void Ruler::paintEvent(QPaintEvent*) { if (!tick_position_cache_) { auto ffunc = [this](const pv::util::Timestamp& t) { return format_time_with_distance( this->view_.tick_period(), t, this->view_.tick_prefix(), this->view_.time_unit(), this->view_.tick_precision()); }; tick_position_cache_ = calculate_tick_positions( view_.tick_period(), view_.ruler_offset(), view_.scale(), width(), view_.minor_tick_count(), ffunc); } const int ValueMargin = 3; const int text_height = calculate_text_height(); const int ruler_height = RulerHeight * text_height; const int major_tick_y1 = text_height + ValueMargin * 2; const int minor_tick_y1 = (major_tick_y1 + ruler_height) / 2; QPainter p(this); // Draw the tick marks p.setPen(palette().color(foregroundRole())); for (const auto& tick: tick_position_cache_->major) { const int leftedge = 0; const int rightedge = width(); const int x_tick = tick.first; if ((x_tick > leftedge) && (x_tick < rightedge)) { const int x_left_bound = QFontMetrics(font()).width(tick.second) / 2; const int x_right_bound = rightedge - x_left_bound; const int x_legend = min(max(x_tick, x_left_bound), x_right_bound); p.drawText(x_legend, ValueMargin, 0, text_height, AlignCenter | AlignTop | TextDontClip, tick.second); p.drawLine(QPointF(x_tick, major_tick_y1), QPointF(tick.first, ruler_height)); } } for (const auto& tick: tick_position_cache_->minor) { p.drawLine(QPointF(tick, minor_tick_y1), QPointF(tick, ruler_height)); } // Draw the hover mark draw_hover_mark(p, text_height); p.setRenderHint(QPainter::Antialiasing); // The cursor labels are not drawn with the arrows exactly on the // bottom line of the widget, because then the selection shadow // would be clipped away. const QRect r = rect().adjusted(0, 0, 0, -ViewItem::HighlightRadius); // Draw the items const vector< shared_ptr > items(view_.time_items()); for (auto &i : items) { const bool highlight = !item_dragging_ && i->label_rect(r).contains(mouse_point_); i->paint_label(p, r, highlight); } } void Ruler::draw_hover_mark(QPainter &p, int text_height) { const int x = view_.hover_point().x(); if (x == -1) return; p.setPen(QPen(Qt::NoPen)); p.setBrush(QBrush(palette().color(foregroundRole()))); const int b = RulerHeight * text_height; const float hover_arrow_size = HoverArrowSize * text_height; const QPointF points[] = { QPointF(x, b), QPointF(x - hover_arrow_size, b - hover_arrow_size), QPointF(x + hover_arrow_size, b - hover_arrow_size) }; p.drawPolygon(points, countof(points)); } int Ruler::calculate_text_height() const { return QFontMetrics(font()).ascent(); } TickPositions Ruler::calculate_tick_positions( const pv::util::Timestamp& major_period, const pv::util::Timestamp& offset, const double scale, const int width, const unsigned int minor_tick_count, function format_function) { TickPositions tp; const pv::util::Timestamp minor_period = major_period / minor_tick_count; const pv::util::Timestamp first_major_division = floor(offset / major_period); const pv::util::Timestamp first_minor_division = ceil(offset / minor_period); const pv::util::Timestamp t0 = first_major_division * major_period; int division = (round(first_minor_division - first_major_division * minor_tick_count)).convert_to() - 1; double x; do { pv::util::Timestamp t = t0 + division * minor_period; x = ((t - offset) / scale).convert_to(); if (division % minor_tick_count == 0) { // Recalculate 't' without using 'minor_period' which is a fraction t = t0 + division / minor_tick_count * major_period; tp.major.emplace_back(x, format_function(t)); } else { tp.minor.emplace_back(x); } division++; } while (x < width); return tp; } void Ruler::on_hover_point_changed(const QWidget* widget, const QPoint &hp) { (void)widget; (void)hp; update(); } void Ruler::invalidate_tick_position_cache() { tick_position_cache_ = boost::none; } void Ruler::on_createMarker() { view_.add_flag(get_time_from_x_pos(mouse_down_point_.x())); } void Ruler::on_setZeroPosition() { view_.set_zero_position(get_time_from_x_pos(mouse_down_point_.x())); } void Ruler::on_toggleHoverMarker() { GlobalSettings settings; const bool state = settings.value(GlobalSettings::Key_View_ShowHoverMarker).toBool(); settings.setValue(GlobalSettings::Key_View_ShowHoverMarker, !state); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/rowitem.hpp000600 001750 001750 00000002221 13365561737 020265 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_ROWITEM_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_ROWITEM_HPP #include "viewitem.hpp" namespace pv { namespace views { namespace trace { class RowItem : public ViewItem { Q_OBJECT public: virtual void hover_point_changed(const QPoint &hp); }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_ROWITEM_HPP pulseview-0.4.1/pv/views/trace/cursorpair.cpp000600 001750 001750 00000013426 13365561737 020774 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include "cursorpair.hpp" #include "pv/globalsettings.hpp" #include "pv/util.hpp" #include "ruler.hpp" #include "view.hpp" using std::max; using std::make_pair; using std::min; using std::shared_ptr; using std::pair; namespace pv { namespace views { namespace trace { const int CursorPair::DeltaPadding = 8; CursorPair::CursorPair(View &view) : TimeItem(view), first_(new Cursor(view, 0.0)), second_(new Cursor(view, 1.0)) { GlobalSettings::add_change_handler(this); GlobalSettings settings; fill_color_ = QColor::fromRgba(settings.value( GlobalSettings::Key_View_CursorFillColor).value()); connect(&view_, SIGNAL(hover_point_changed(const QWidget*, QPoint)), this, SLOT(on_hover_point_changed(const QWidget*, QPoint))); } CursorPair::~CursorPair() { GlobalSettings::remove_change_handler(this); } bool CursorPair::enabled() const { return view_.cursors_shown(); } shared_ptr CursorPair::first() const { return first_; } shared_ptr CursorPair::second() const { return second_; } 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); } float CursorPair::get_x() const { return (first_->get_x() + second_->get_x()) / 2.0f; } QPoint CursorPair::drag_point(const QRect &rect) const { return first_->drag_point(rect); } pv::widgets::Popup* CursorPair::create_popup(QWidget *parent) { (void)parent; return nullptr; } QRectF CursorPair::label_rect(const QRectF &rect) const { const QSizeF label_size(text_size_ + LabelPadding * 2); const pair offsets(get_cursor_offsets()); const pair normal_offsets( (offsets.first < offsets.second) ? offsets : make_pair(offsets.second, offsets.first)); const float height = label_size.height(); const float left = max(normal_offsets.first + DeltaPadding, -height); const float right = min(normal_offsets.second - DeltaPadding, (float)rect.width() + height); return QRectF(left, rect.height() - label_size.height() - TimeMarker::ArrowSize - 0.5f, right - left, height); } void CursorPair::paint_label(QPainter &p, const QRect &rect, bool hover) { assert(first_); assert(second_); if (!enabled()) return; const QColor text_color = ViewItem::select_text_color(Cursor::FillColor); p.setPen(text_color); QString text = format_string(); text_size_ = p.boundingRect(QRectF(), 0, text).size(); QRectF delta_rect(label_rect(rect)); const int radius = delta_rect.height() / 2; QRectF text_rect(delta_rect.intersected(rect).adjusted(radius, 0, -radius, 0)); if (text_rect.width() < text_size_.width()) { text = "..."; text_size_ = p.boundingRect(QRectF(), 0, text).size(); label_incomplete_ = true; } else label_incomplete_ = false; if (selected()) { p.setBrush(Qt::transparent); p.setPen(highlight_pen()); p.drawRoundedRect(delta_rect, radius, radius); } p.setBrush(hover ? Cursor::FillColor.lighter() : Cursor::FillColor); p.setPen(Cursor::FillColor.darker()); p.drawRoundedRect(delta_rect, radius, radius); delta_rect.adjust(1, 1, -1, -1); p.setPen(Cursor::FillColor.lighter()); const int highlight_radius = delta_rect.height() / 2 - 2; p.drawRoundedRect(delta_rect, highlight_radius, highlight_radius); label_area_ = delta_rect; p.setPen(text_color); p.drawText(text_rect, Qt::AlignCenter | Qt::AlignVCenter, text); } void CursorPair::paint_back(QPainter &p, ViewItemPaintParams &pp) { if (!enabled()) return; p.setPen(Qt::NoPen); p.setBrush(fill_color_); const pair offsets(get_cursor_offsets()); const int l = (int)max(min(offsets.first, offsets.second), 0.0f); const int r = (int)min(max(offsets.first, offsets.second), (float)pp.width()); p.drawRect(l, pp.top(), r - l, pp.height()); } QString CursorPair::format_string() { const pv::util::SIPrefix prefix = view_.tick_prefix(); const pv::util::Timestamp diff = abs(second_->time() - first_->time()); const QString s1 = Ruler::format_time_with_distance( diff, diff, prefix, view_.time_unit(), 12, false); /* Always use 12 precision digits */ const QString s2 = util::format_time_si( 1 / diff, pv::util::SIPrefix::unspecified, 4, "Hz", false); return QString("%1 / %2").arg(s1, s2); } pair CursorPair::get_cursor_offsets() const { assert(first_); assert(second_); return pair(first_->get_x(), second_->get_x()); } void CursorPair::on_setting_changed(const QString &key, const QVariant &value) { if (key == GlobalSettings::Key_View_CursorFillColor) fill_color_ = QColor::fromRgba(value.value()); } void CursorPair::on_hover_point_changed(const QWidget* widget, const QPoint& hp) { if (widget != view_.ruler()) return; if (!label_incomplete_) return; if (label_area_.contains(hp)) QToolTip::showText(view_.mapToGlobal(hp), format_string()); else QToolTip::hideText(); // TODO Will break other tooltips when there can be others } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/analogsignal.cpp000600 001750 001750 00000121112 13365561737 021232 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "analogsignal.hpp" #include "logicsignal.hpp" #include "view.hpp" #include "pv/util.hpp" #include "pv/data/analog.hpp" #include "pv/data/analogsegment.hpp" #include "pv/data/logic.hpp" #include "pv/data/logicsegment.hpp" #include "pv/data/signalbase.hpp" #include "pv/globalsettings.hpp" #include using std::deque; using std::div; using std::div_t; // Note that "using std::isnan;" is _not_ put here since that would break // compilation on some platforms. Use "std::isnan()" instead in checks below. using std::max; using std::make_pair; using std::min; using std::numeric_limits; using std::out_of_range; using std::pair; using std::shared_ptr; using std::vector; using pv::data::LogicSegment; using pv::data::SignalBase; using pv::util::SIPrefix; namespace pv { namespace views { namespace trace { const QColor AnalogSignal::SignalColors[4] = { QColor(0xC4, 0xA0, 0x00), // Yellow QColor(0x87, 0x20, 0x7A), // Magenta QColor(0x20, 0x4A, 0x87), // Blue QColor(0x4E, 0x9A, 0x06) // Green }; const QPen AnalogSignal::AxisPen(QColor(0, 0, 0, 30 * 256 / 100), 2); const QColor AnalogSignal::GridMajorColor = QColor(0, 0, 0, 40 * 256 / 100); const QColor AnalogSignal::GridMinorColor = QColor(0, 0, 0, 20 * 256 / 100); const QColor AnalogSignal::SamplingPointColor(0x77, 0x77, 0x77); const QColor AnalogSignal::SamplingPointColorLo = QColor(200, 0, 0, 80 * 256 / 100); const QColor AnalogSignal::SamplingPointColorNe = QColor(0, 0, 0, 80 * 256 / 100); const QColor AnalogSignal::SamplingPointColorHi = QColor(0, 200, 0, 80 * 256 / 100); const QColor AnalogSignal::ThresholdColor = QColor(0, 0, 0, 30 * 256 / 100); const QColor AnalogSignal::ThresholdColorLo = QColor(255, 0, 0, 8 * 256 / 100); const QColor AnalogSignal::ThresholdColorNe = QColor(0, 0, 0, 10 * 256 / 100); const QColor AnalogSignal::ThresholdColorHi = QColor(0, 255, 0, 8 * 256 / 100); const int64_t AnalogSignal::TracePaintBlockSize = 1024 * 1024; // 4 MiB (due to float) const float AnalogSignal::EnvelopeThreshold = 64.0f; const int AnalogSignal::MaximumVDivs = 10; const int AnalogSignal::MinScaleIndex = -6; const int AnalogSignal::MaxScaleIndex = 7; const int AnalogSignal::InfoTextMarginRight = 20; const int AnalogSignal::InfoTextMarginBottom = 5; AnalogSignal::AnalogSignal( pv::Session &session, shared_ptr base) : Signal(session, base), scale_index_(4), // 20 per div pos_vdivs_(1), neg_vdivs_(1), resolution_(0), display_type_(DisplayBoth), autoranging_(true), value_at_hover_pos_(std::numeric_limits::quiet_NaN()) { axis_pen_ = AxisPen; pv::data::Analog* analog_data = dynamic_cast(data().get()); connect(analog_data, SIGNAL(min_max_changed(float, float)), this, SLOT(on_min_max_changed(float, float))); GlobalSettings settings; show_sampling_points_ = settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool(); fill_high_areas_ = settings.value(GlobalSettings::Key_View_FillSignalHighAreas).toBool(); high_fill_color_ = QColor::fromRgba(settings.value( GlobalSettings::Key_View_FillSignalHighAreaColor).value()); show_analog_minor_grid_ = settings.value(GlobalSettings::Key_View_ShowAnalogMinorGrid).toBool(); conversion_threshold_disp_mode_ = settings.value(GlobalSettings::Key_View_ConversionThresholdDispMode).toInt(); div_height_ = settings.value(GlobalSettings::Key_View_DefaultDivHeight).toInt(); base_->set_color(SignalColors[base_->index() % countof(SignalColors)]); update_scale(); } shared_ptr AnalogSignal::data() const { return base_->analog_data(); } void AnalogSignal::save_settings(QSettings &settings) const { settings.setValue("pos_vdivs", pos_vdivs_); settings.setValue("neg_vdivs", neg_vdivs_); settings.setValue("scale_index", scale_index_); settings.setValue("display_type", display_type_); settings.setValue("autoranging", autoranging_); settings.setValue("div_height", div_height_); } void AnalogSignal::restore_settings(QSettings &settings) { if (settings.contains("pos_vdivs")) pos_vdivs_ = settings.value("pos_vdivs").toInt(); if (settings.contains("neg_vdivs")) neg_vdivs_ = settings.value("neg_vdivs").toInt(); if (settings.contains("scale_index")) { scale_index_ = settings.value("scale_index").toInt(); update_scale(); } if (settings.contains("display_type")) display_type_ = (DisplayType)(settings.value("display_type").toInt()); if (settings.contains("autoranging")) autoranging_ = settings.value("autoranging").toBool(); if (settings.contains("div_height")) { const int old_height = div_height_; div_height_ = settings.value("div_height").toInt(); if ((div_height_ != old_height) && owner_) { // Call order is important, otherwise the lazy event handler won't work owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } } } pair AnalogSignal::v_extents() const { const int ph = pos_vdivs_ * div_height_; const int nh = neg_vdivs_ * div_height_; return make_pair(-ph, nh); } void AnalogSignal::paint_back(QPainter &p, ViewItemPaintParams &pp) { if (!base_->enabled()) return; bool paint_thr_bg = conversion_threshold_disp_mode_ == GlobalSettings::ConvThrDispMode_Background; const vector thresholds = base_->get_conversion_thresholds(); // Only display thresholds if we have some and we show analog samples if ((thresholds.size() > 0) && paint_thr_bg && ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth))) { const int visual_y = get_visual_y(); const pair extents = v_extents(); const int top = visual_y + extents.first; const int btm = visual_y + extents.second; // Draw high/neutral/low areas if (thresholds.size() == 2) { int thr_lo = visual_y - thresholds[0] * scale_; int thr_hi = visual_y - thresholds[1] * scale_; thr_lo = min(max(thr_lo, top), btm); thr_hi = min(max(thr_hi, top), btm); p.fillRect(QRectF(pp.left(), top, pp.width(), thr_hi - top), QBrush(ThresholdColorHi)); p.fillRect(QRectF(pp.left(), thr_hi, pp.width(), thr_lo - thr_hi), QBrush(ThresholdColorNe)); p.fillRect(QRectF(pp.left(), thr_lo, pp.width(), btm - thr_lo), QBrush(ThresholdColorLo)); } else { int thr = visual_y - thresholds[0] * scale_; thr = min(max(thr, top), btm); p.fillRect(QRectF(pp.left(), top, pp.width(), thr - top), QBrush(ThresholdColorHi)); p.fillRect(QRectF(pp.left(), thr, pp.width(), btm - thr), QBrush(ThresholdColorLo)); } paint_axis(p, pp, get_visual_y()); } else { Signal::paint_back(p, pp); paint_axis(p, pp, get_visual_y()); } } void AnalogSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp) { assert(base_->analog_data()); assert(owner_); const int y = get_visual_y(); if (!base_->enabled()) return; if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) { paint_grid(p, y, pp.left(), pp.right()); shared_ptr segment = get_analog_segment_to_paint(); if (!segment || (segment->get_sample_count() == 0)) return; const double pixels_offset = pp.pixels_offset(); const double samplerate = max(1.0, segment->samplerate()); const pv::util::Timestamp& start_time = segment->start_time(); const int64_t last_sample = (int64_t)segment->get_sample_count() - 1; const double samples_per_pixel = samplerate * pp.scale(); 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(floor(start).convert_to(), (int64_t)0), last_sample); const int64_t end_sample = min(max((ceil(end) + 1).convert_to(), (int64_t)0), last_sample); if (samples_per_pixel < EnvelopeThreshold) paint_trace(p, segment, y, pp.left(), start_sample, end_sample, pixels_offset, samples_per_pixel); else paint_envelope(p, segment, y, pp.left(), start_sample, end_sample, pixels_offset, samples_per_pixel); } if ((display_type_ == DisplayConverted) || (display_type_ == DisplayBoth)) paint_logic_mid(p, pp); } void AnalogSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp) { if (!enabled()) return; if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) { const int y = get_visual_y(); QString infotext; // Show the info section on the right side of the trace, including // the value at the hover point when the hover marker is enabled // and we have corresponding data available if (show_hover_marker_ && !std::isnan(value_at_hover_pos_)) { infotext = QString("[%1] %2 V/div") .arg(format_value_si(value_at_hover_pos_, SIPrefix::unspecified, 0, "V", false)) .arg(resolution_); } else infotext = QString("%1 V/div").arg(resolution_); p.setPen(base_->color()); p.setFont(QApplication::font()); const QRectF bounding_rect = QRectF(pp.left(), y + v_extents().first, pp.width() - InfoTextMarginRight, v_extents().second - v_extents().first - InfoTextMarginBottom); p.drawText(bounding_rect, Qt::AlignRight | Qt::AlignBottom, infotext); } if (show_hover_marker_) paint_hover_marker(p); } void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right) { p.setRenderHint(QPainter::Antialiasing, false); if (pos_vdivs_ > 0) { p.setPen(QPen(GridMajorColor, 1, Qt::DashLine)); for (int i = 1; i <= pos_vdivs_; i++) { const float dy = i * div_height_; p.drawLine(QLineF(left, y - dy, right, y - dy)); } } if ((pos_vdivs_ > 0) && show_analog_minor_grid_) { p.setPen(QPen(GridMinorColor, 1, Qt::DashLine)); for (int i = 0; i < pos_vdivs_; i++) { const float dy = i * div_height_; const float dy25 = dy + (0.25 * div_height_); const float dy50 = dy + (0.50 * div_height_); const float dy75 = dy + (0.75 * div_height_); p.drawLine(QLineF(left, y - dy25, right, y - dy25)); p.drawLine(QLineF(left, y - dy50, right, y - dy50)); p.drawLine(QLineF(left, y - dy75, right, y - dy75)); } } if (neg_vdivs_ > 0) { p.setPen(QPen(GridMajorColor, 1, Qt::DashLine)); for (int i = 1; i <= neg_vdivs_; i++) { const float dy = i * div_height_; p.drawLine(QLineF(left, y + dy, right, y + dy)); } } if ((pos_vdivs_ > 0) && show_analog_minor_grid_) { p.setPen(QPen(GridMinorColor, 1, Qt::DashLine)); for (int i = 0; i < neg_vdivs_; i++) { const float dy = i * div_height_; const float dy25 = dy + (0.25 * div_height_); const float dy50 = dy + (0.50 * div_height_); const float dy75 = dy + (0.75 * div_height_); p.drawLine(QLineF(left, y + dy25, right, y + dy25)); p.drawLine(QLineF(left, y + dy50, right, y + dy50)); p.drawLine(QLineF(left, y + dy75, right, y + dy75)); } } p.setRenderHint(QPainter::Antialiasing, true); } void AnalogSignal::paint_trace(QPainter &p, const shared_ptr &segment, int y, int left, const int64_t start, const int64_t end, const double pixels_offset, const double samples_per_pixel) { if (end <= start) return; bool paint_thr_dots = (base_->get_conversion_type() != data::SignalBase::NoConversion) && (conversion_threshold_disp_mode_ == GlobalSettings::ConvThrDispMode_Dots); vector thresholds; if (paint_thr_dots) thresholds = base_->get_conversion_thresholds(); // Calculate and paint the sampling points if enabled and useful GlobalSettings settings; const bool show_sampling_points = (show_sampling_points_ || paint_thr_dots) && (samples_per_pixel < 0.25); p.setPen(base_->color()); const int64_t points_count = end - start + 1; QPointF *points = new QPointF[points_count]; QPointF *point = points; vector sampling_points[3]; int64_t sample_count = min(points_count, TracePaintBlockSize); int64_t block_sample = 0; float *sample_block = new float[TracePaintBlockSize]; segment->get_samples(start, start + sample_count, sample_block); if (show_hover_marker_) reset_pixel_values(); const int w = 2; for (int64_t sample = start; sample <= end; sample++, block_sample++) { // Fetch next block of samples if we finished the current one if (block_sample == TracePaintBlockSize) { block_sample = 0; sample_count = min(points_count - sample, TracePaintBlockSize); segment->get_samples(sample, sample + sample_count, sample_block); } const float abs_x = sample / samples_per_pixel - pixels_offset; const float x = left + abs_x; *point++ = QPointF(x, y - sample_block[block_sample] * scale_); // Generate the pixel<->value lookup table for the mouse hover if (show_hover_marker_) process_next_sample_value(abs_x, sample_block[block_sample]); // Create the sampling points if needed if (show_sampling_points) { int idx = 0; // Neutral if (paint_thr_dots) { if (thresholds.size() == 1) idx = (sample_block[block_sample] >= thresholds[0]) ? 2 : 1; else if (thresholds.size() == 2) { if (sample_block[block_sample] > thresholds[1]) idx = 2; // High else if (sample_block[block_sample] < thresholds[0]) idx = 1; // Low } } sampling_points[idx].emplace_back(x - (w / 2), y - sample_block[block_sample] * scale_ - (w / 2), w, w); } } delete[] sample_block; p.drawPolyline(points, points_count); if (show_sampling_points) { if (paint_thr_dots) { p.setPen(SamplingPointColorNe); p.drawRects(sampling_points[0].data(), sampling_points[0].size()); p.setPen(SamplingPointColorLo); p.drawRects(sampling_points[1].data(), sampling_points[1].size()); p.setPen(SamplingPointColorHi); p.drawRects(sampling_points[2].data(), sampling_points[2].size()); } else { p.setPen(SamplingPointColor); p.drawRects(sampling_points[0].data(), sampling_points[0].size()); } } delete[] points; } void AnalogSignal::paint_envelope(QPainter &p, const shared_ptr &segment, int y, int left, const int64_t start, const int64_t end, const double pixels_offset, const double samples_per_pixel) { using pv::data::AnalogSegment; // Note: Envelope painting currently doesn't generate a pixel<->value lookup table if (show_hover_marker_) reset_pixel_values(); AnalogSegment::EnvelopeSection e; segment->get_envelope_section(e, start, end, samples_per_pixel); if (e.length < 2) return; p.setPen(QPen(Qt::NoPen)); p.setBrush(base_->color()); QRectF *const rects = new QRectF[e.length]; QRectF *rect = rects; for (uint64_t sample = 0; sample < e.length - 1; sample++) { const float x = ((e.scale * sample + e.start) / samples_per_pixel - pixels_offset) + left; const AnalogSegment::EnvelopeSample *const s = e.samples + sample; // We overlap this sample with the next so that vertical // gaps do not appear during steep rising or falling edges const float b = y - max(s->max, (s + 1)->min) * scale_; const float t = y - min(s->min, (s + 1)->max) * scale_; float h = b - t; if (h >= 0.0f && h <= 1.0f) h = 1.0f; if (h <= 0.0f && h >= -1.0f) h = -1.0f; *rect++ = QRectF(x, t, 1.0f, h); } p.drawRects(rects, e.length); delete[] rects; delete[] e.samples; } void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp) { QLineF *line; vector< pair > edges; assert(base_); const int y = get_visual_y(); if (!base_->enabled() || !base_->logic_data()) return; const int signal_margin = QFontMetrics(QApplication::font()).height() / 2; const int ph = min(pos_vdivs_, 1) * div_height_; const int nh = min(neg_vdivs_, 1) * div_height_; const float high_offset = y - ph + signal_margin + 0.5f; const float low_offset = y + nh - signal_margin - 0.5f; const float signal_height = low_offset - high_offset; 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; const double pixels_offset = pp.pixels_offset(); const pv::util::Timestamp& start_time = segment->start_time(); const int64_t last_sample = (int64_t)segment->get_sample_count() - 1; const double samples_per_pixel = samplerate * pp.scale(); const double pixels_per_sample = 1 / samples_per_pixel; 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(floor(start).convert_to(), (int64_t)0), last_sample); const uint64_t end_sample = min(max(ceil(end).convert_to(), (int64_t)0), last_sample); segment->get_subsampled_edges(edges, start_sample, end_sample, samples_per_pixel / LogicSignal::Oversampling, 0); assert(edges.size() >= 2); const float first_sample_x = pp.left() + (edges.front().first / samples_per_pixel - pixels_offset); const float last_sample_x = pp.left() + (edges.back().first / samples_per_pixel - pixels_offset); // Check whether we need to paint the sampling points const bool show_sampling_points = show_sampling_points_ && (samples_per_pixel < 0.25); vector sampling_points; float sampling_point_x = first_sample_x; int64_t sampling_point_sample = start_sample; const int w = 2; if (show_sampling_points) sampling_points.reserve(end_sample - start_sample + 1); vector high_rects; float rising_edge_x; bool rising_edge_seen = false; // Paint the edges const unsigned int edge_count = edges.size() - 2; QLineF *const edge_lines = new QLineF[edge_count]; line = edge_lines; if (edges.front().second) { // Beginning of trace is high rising_edge_x = first_sample_x; rising_edge_seen = true; } for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) { // Note: multiple edges occupying a single pixel are represented by an edge // with undefined logic level. This means that only the first falling edge // after a rising edge corresponds to said rising edge - and vice versa. If // more edges with the same logic level follow, they denote multiple edges. const float x = pp.left() + ((*i).first / samples_per_pixel - pixels_offset); *line++ = QLineF(x, high_offset, x, low_offset); if (fill_high_areas_) { // Any edge terminates a high area if (rising_edge_seen) { const int width = x - rising_edge_x; if (width > 0) high_rects.emplace_back(rising_edge_x, high_offset, width, signal_height); rising_edge_seen = false; } // Only rising edges start high areas if ((*i).second) { rising_edge_x = x; rising_edge_seen = true; } } if (show_sampling_points) while (sampling_point_sample < (*i).first) { const float y = (*i).second ? low_offset : high_offset; sampling_points.emplace_back( QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); sampling_point_sample++; sampling_point_x += pixels_per_sample; }; } // Calculate the sample points from the last edge to the end of the trace if (show_sampling_points) while ((uint64_t)sampling_point_sample <= end_sample) { // Signal changed after the last edge, so the level is inverted const float y = (edges.cend() - 1)->second ? high_offset : low_offset; sampling_points.emplace_back( QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w)); sampling_point_sample++; sampling_point_x += pixels_per_sample; }; if (fill_high_areas_) { // Add last high rectangle if the signal is still high at the end of the trace if (rising_edge_seen && (edges.cend() - 1)->second) high_rects.emplace_back(rising_edge_x, high_offset, last_sample_x - rising_edge_x, signal_height); p.setPen(high_fill_color_); p.setBrush(high_fill_color_); p.drawRects((const QRectF*)(high_rects.data()), high_rects.size()); } p.setPen(LogicSignal::EdgeColor); p.drawLines(edge_lines, edge_count); delete[] edge_lines; // Paint the caps const unsigned int max_cap_line_count = edges.size(); QLineF *const cap_lines = new QLineF[max_cap_line_count]; p.setPen(LogicSignal::HighColor); paint_logic_caps(p, cap_lines, edges, true, samples_per_pixel, pixels_offset, pp.left(), high_offset); p.setPen(LogicSignal::LowColor); paint_logic_caps(p, cap_lines, edges, false, samples_per_pixel, pixels_offset, pp.left(), low_offset); delete[] cap_lines; // Paint the sampling points if (show_sampling_points) { p.setPen(SamplingPointColor); p.drawRects(sampling_points.data(), sampling_points.size()); } } void AnalogSignal::paint_logic_caps(QPainter &p, QLineF *const lines, vector< pair > &edges, bool level, double samples_per_pixel, double pixels_offset, float x_offset, float y_offset) { QLineF *line = lines; for (auto i = edges.begin(); i != (edges.end() - 1); i++) if ((*i).second == level) { *line++ = QLineF( ((*i).first / samples_per_pixel - pixels_offset) + x_offset, y_offset, ((*(i+1)).first / samples_per_pixel - pixels_offset) + x_offset, y_offset); } p.drawLines(lines, line - lines); } shared_ptr AnalogSignal::get_analog_segment_to_paint() const { shared_ptr segment; const deque< shared_ptr > &segments = base_->analog_data()->analog_segments(); if (!segments.empty()) { if (segment_display_mode_ == ShowLastSegmentOnly) segment = segments.back(); if ((segment_display_mode_ == ShowSingleSegmentOnly) || (segment_display_mode_ == ShowLastCompleteSegmentOnly)) { try { segment = segments.at(current_segment_); } catch (out_of_range&) { qDebug() << "Current analog segment out of range for signal" << base_->name() << ":" << current_segment_; } } } return segment; } shared_ptr AnalogSignal::get_logic_segment_to_paint() const { shared_ptr segment; const deque< shared_ptr > &segments = base_->logic_data()->logic_segments(); if (!segments.empty()) { if (segment_display_mode_ == ShowLastSegmentOnly) segment = segments.back(); if ((segment_display_mode_ == ShowSingleSegmentOnly) || (segment_display_mode_ == ShowLastCompleteSegmentOnly)) { try { segment = segments.at(current_segment_); } catch (out_of_range&) { qDebug() << "Current logic segment out of range for signal" << base_->name() << ":" << current_segment_; } } } return segment; } float AnalogSignal::get_resolution(int scale_index) { const float seq[] = {1.0f, 2.0f, 5.0f}; const int offset = numeric_limits::max() / (2 * countof(seq)); const div_t d = div((int)(scale_index + countof(seq) * offset), countof(seq)); return powf(10.0f, d.quot - offset) * seq[d.rem]; } void AnalogSignal::update_scale() { resolution_ = get_resolution(scale_index_); scale_ = div_height_ / resolution_; } void AnalogSignal::update_conversion_widgets() { SignalBase::ConversionType conv_type = base_->get_conversion_type(); // Enable or disable widgets depending on conversion state conv_threshold_cb_->setEnabled(conv_type != SignalBase::NoConversion); display_type_cb_->setEnabled(conv_type != SignalBase::NoConversion); conv_threshold_cb_->clear(); vector < pair > presets = base_->get_conversion_presets(); // Prevent the combo box from firing the "edit text changed" signal // as that would involuntarily select the first entry conv_threshold_cb_->blockSignals(true); // Set available options depending on chosen conversion for (pair& preset : presets) conv_threshold_cb_->addItem(preset.first, preset.second); map < QString, QVariant > options = base_->get_conversion_options(); if (conv_type == SignalBase::A2LConversionByThreshold) { const vector thresholds = base_->get_conversion_thresholds( SignalBase::A2LConversionByThreshold, true); conv_threshold_cb_->addItem( QString("%1V").arg(QString::number(thresholds[0], 'f', 1)), -1); } if (conv_type == SignalBase::A2LConversionBySchmittTrigger) { const vector thresholds = base_->get_conversion_thresholds( SignalBase::A2LConversionBySchmittTrigger, true); conv_threshold_cb_->addItem(QString("%1V/%2V").arg( QString::number(thresholds[0], 'f', 1), QString::number(thresholds[1], 'f', 1)), -1); } int preset_id = base_->get_current_conversion_preset(); conv_threshold_cb_->setCurrentIndex( conv_threshold_cb_->findData(preset_id)); 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 = base_->analog_data()->analog_segments(); if (segments.empty()) return; static double prev_min = 0, prev_max = 0; double min = 0, max = 0; for (const shared_ptr& segment : segments) { pair mm = segment->get_min_max(); min = std::min(min, mm.first); max = std::max(max, mm.second); } if ((min == prev_min) && (max == prev_max) && !force_update) return; prev_min = min; prev_max = max; // If we're allowed to alter the div assignment... if (!keep_divs) { // Use all divs for the positive range if there are no negative values if ((min == 0) && (neg_vdivs_ > 0)) { pos_vdivs_ += neg_vdivs_; neg_vdivs_ = 0; } // Split up the divs if there are negative values but no negative divs if ((min < 0) && (neg_vdivs_ == 0)) { neg_vdivs_ = pos_vdivs_ / 2; pos_vdivs_ -= neg_vdivs_; } } // If there is still no positive div when we need it, add one // (this can happen when pos_vdivs==neg_vdivs==0) if ((max > 0) && (pos_vdivs_ == 0)) { pos_vdivs_ = 1; owner_->extents_changed(false, true); } // If there is still no negative div when we need it, add one // (this can happen when pos_vdivs was 0 or 1 when trying to split) if ((min < 0) && (neg_vdivs_ == 0)) { neg_vdivs_ = 1; owner_->extents_changed(false, true); } double min_value_per_div; if ((pos_vdivs_ > 0) && (neg_vdivs_ > 0)) min_value_per_div = std::max(max / pos_vdivs_, -min / neg_vdivs_); else if (pos_vdivs_ > 0) min_value_per_div = max / pos_vdivs_; else min_value_per_div = -min / neg_vdivs_; // Find first scale value that is bigger than the value we need for (int i = MinScaleIndex; i < MaxScaleIndex; i++) if (get_resolution(i) > min_value_per_div) { scale_index_ = i; break; } update_scale(); } void AnalogSignal::reset_pixel_values() { value_at_pixel_pos_.clear(); current_pixel_pos_ = -1; prev_value_at_pixel_ = std::numeric_limits::quiet_NaN(); } void AnalogSignal::process_next_sample_value(float x, float value) { // Note: NAN is used to indicate the non-existance of a value at this pixel if (std::isnan(prev_value_at_pixel_)) { if (x < 0) { min_value_at_pixel_ = value; max_value_at_pixel_ = value; prev_value_at_pixel_ = value; current_pixel_pos_ = x; } else prev_value_at_pixel_ = std::numeric_limits::quiet_NaN(); } const int pixel_pos = (int)(x + 0.5); if (pixel_pos > current_pixel_pos_) { if (pixel_pos - current_pixel_pos_ == 1) { if (std::isnan(prev_value_at_pixel_)) { value_at_pixel_pos_.push_back(prev_value_at_pixel_); } else { // Average the min/max range to create one value for the previous pixel const float avg = (min_value_at_pixel_ + max_value_at_pixel_) / 2; value_at_pixel_pos_.push_back(avg); } } else { // Interpolate values to create values for the intermediate pixels const float start_value = prev_value_at_pixel_; const float end_value = value; const int steps = fabs(pixel_pos - current_pixel_pos_); const double gradient = (end_value - start_value) / steps; for (int i = 0; i < steps; i++) { if (current_pixel_pos_ + i < 0) continue; value_at_pixel_pos_.push_back(start_value + i * gradient); } } min_value_at_pixel_ = value; max_value_at_pixel_ = value; prev_value_at_pixel_ = value; current_pixel_pos_ = pixel_pos; } else { // Another sample for the same pixel if (value < min_value_at_pixel_) min_value_at_pixel_ = value; if (value > max_value_at_pixel_) max_value_at_pixel_ = value; } } void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form) { // Add the standard options Signal::populate_popup_form(parent, form); QFormLayout *const layout = new QFormLayout; // Add div-related settings pvdiv_sb_ = new QSpinBox(parent); pvdiv_sb_->setRange(0, MaximumVDivs); pvdiv_sb_->setValue(pos_vdivs_); connect(pvdiv_sb_, SIGNAL(valueChanged(int)), this, SLOT(on_pos_vdivs_changed(int))); layout->addRow(tr("Number of pos vertical divs"), pvdiv_sb_); nvdiv_sb_ = new QSpinBox(parent); nvdiv_sb_->setRange(0, MaximumVDivs); nvdiv_sb_->setValue(neg_vdivs_); connect(nvdiv_sb_, SIGNAL(valueChanged(int)), this, SLOT(on_neg_vdivs_changed(int))); layout->addRow(tr("Number of neg vertical divs"), nvdiv_sb_); div_height_sb_ = new QSpinBox(parent); div_height_sb_->setRange(20, 1000); div_height_sb_->setSingleStep(5); div_height_sb_->setSuffix(tr(" pixels")); div_height_sb_->setValue(div_height_); connect(div_height_sb_, SIGNAL(valueChanged(int)), this, SLOT(on_div_height_changed(int))); layout->addRow(tr("Div height"), div_height_sb_); // Add the vertical resolution resolution_cb_ = new QComboBox(parent); for (int i = MinScaleIndex; i < MaxScaleIndex; i++) { const QString label = QString("%1").arg(get_resolution(i)); resolution_cb_->insertItem(0, label, QVariant(i)); } int cur_idx = resolution_cb_->findData(QVariant(scale_index_)); resolution_cb_->setCurrentIndex(cur_idx); connect(resolution_cb_, SIGNAL(currentIndexChanged(int)), this, SLOT(on_resolution_changed(int))); QGridLayout *const vdiv_layout = new QGridLayout; QLabel *const vdiv_unit = new QLabel(tr("V/div")); vdiv_layout->addWidget(resolution_cb_, 0, 0); vdiv_layout->addWidget(vdiv_unit, 0, 1); layout->addRow(tr("Vertical resolution"), vdiv_layout); // Add the autoranging checkbox QCheckBox* autoranging_cb = new QCheckBox(); autoranging_cb->setCheckState(autoranging_ ? Qt::Checked : Qt::Unchecked); connect(autoranging_cb, SIGNAL(stateChanged(int)), this, SLOT(on_autoranging_changed(int))); layout->addRow(tr("Autoranging"), autoranging_cb); // Add the conversion type dropdown conversion_cb_ = new QComboBox(); conversion_cb_->addItem(tr("none"), SignalBase::NoConversion); conversion_cb_->addItem(tr("to logic via threshold"), SignalBase::A2LConversionByThreshold); conversion_cb_->addItem(tr("to logic via schmitt-trigger"), SignalBase::A2LConversionBySchmittTrigger); cur_idx = conversion_cb_->findData(QVariant(base_->get_conversion_type())); conversion_cb_->setCurrentIndex(cur_idx); layout->addRow(tr("Conversion"), conversion_cb_); connect(conversion_cb_, SIGNAL(currentIndexChanged(int)), this, SLOT(on_conversion_changed(int))); // Add the conversion threshold settings conv_threshold_cb_ = new QComboBox(); conv_threshold_cb_->setEditable(true); layout->addRow(tr("Conversion threshold(s)"), conv_threshold_cb_); connect(conv_threshold_cb_, SIGNAL(currentIndexChanged(int)), this, SLOT(on_conv_threshold_changed(int))); connect(conv_threshold_cb_, SIGNAL(editTextChanged(const QString&)), this, SLOT(on_conv_threshold_changed())); // index will be -1 // Add the display type dropdown display_type_cb_ = new QComboBox(); display_type_cb_->addItem(tr("analog"), DisplayAnalog); display_type_cb_->addItem(tr("converted"), DisplayConverted); display_type_cb_->addItem(tr("analog+converted"), DisplayBoth); cur_idx = display_type_cb_->findData(QVariant(display_type_)); display_type_cb_->setCurrentIndex(cur_idx); layout->addRow(tr("Show traces for"), display_type_cb_); connect(display_type_cb_, SIGNAL(currentIndexChanged(int)), this, SLOT(on_display_type_changed(int))); // Update the conversion widget contents and states update_conversion_widgets(); form->addRow(layout); } void AnalogSignal::hover_point_changed(const QPoint &hp) { Signal::hover_point_changed(hp); // Note: Even though the view area begins at 0, we exclude 0 because // that's also the value given when the cursor is over the header to the // left of the trace paint area if (hp.x() <= 0) { value_at_hover_pos_ = std::numeric_limits::quiet_NaN(); } else { try { value_at_hover_pos_ = value_at_pixel_pos_.at(hp.x()); } catch (out_of_range&) { value_at_hover_pos_ = std::numeric_limits::quiet_NaN(); } } } void AnalogSignal::on_setting_changed(const QString &key, const QVariant &value) { Signal::on_setting_changed(key, value); if (key == GlobalSettings::Key_View_ShowSamplingPoints) show_sampling_points_ = value.toBool(); if (key == GlobalSettings::Key_View_FillSignalHighAreas) fill_high_areas_ = value.toBool(); if (key == GlobalSettings::Key_View_FillSignalHighAreaColor) high_fill_color_ = QColor::fromRgba(value.value()); if (key == GlobalSettings::Key_View_ShowAnalogMinorGrid) show_analog_minor_grid_ = value.toBool(); if (key == GlobalSettings::Key_View_ConversionThresholdDispMode) { conversion_threshold_disp_mode_ = value.toInt(); if (owner_) owner_->row_item_appearance_changed(false, true); } } void AnalogSignal::on_min_max_changed(float min, float max) { (void)min; (void)max; if (autoranging_) perform_autoranging(false, false); } void AnalogSignal::on_pos_vdivs_changed(int vdivs) { if (vdivs == pos_vdivs_) return; pos_vdivs_ = vdivs; // There has to be at least one div, positive or negative if ((neg_vdivs_ == 0) && (pos_vdivs_ == 0)) { pos_vdivs_ = 1; if (pvdiv_sb_) pvdiv_sb_->setValue(pos_vdivs_); } if (autoranging_) { perform_autoranging(true, true); // It could be that a positive or negative div was added, so update if (pvdiv_sb_) { pvdiv_sb_->setValue(pos_vdivs_); nvdiv_sb_->setValue(neg_vdivs_); } } if (owner_) { // Call order is important, otherwise the lazy event handler won't work owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } } void AnalogSignal::on_neg_vdivs_changed(int vdivs) { if (vdivs == neg_vdivs_) return; neg_vdivs_ = vdivs; // There has to be at least one div, positive or negative if ((neg_vdivs_ == 0) && (pos_vdivs_ == 0)) { pos_vdivs_ = 1; if (pvdiv_sb_) pvdiv_sb_->setValue(pos_vdivs_); } if (autoranging_) { perform_autoranging(true, true); // It could be that a positive or negative div was added, so update if (pvdiv_sb_) { pvdiv_sb_->setValue(pos_vdivs_); nvdiv_sb_->setValue(neg_vdivs_); } } if (owner_) { // Call order is important, otherwise the lazy event handler won't work owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } } void AnalogSignal::on_div_height_changed(int height) { div_height_ = height; update_scale(); if (owner_) { // Call order is important, otherwise the lazy event handler won't work owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } } void AnalogSignal::on_resolution_changed(int index) { scale_index_ = resolution_cb_->itemData(index).toInt(); update_scale(); if (owner_) owner_->row_item_appearance_changed(false, true); } void AnalogSignal::on_autoranging_changed(int state) { autoranging_ = (state == Qt::Checked); if (autoranging_) perform_autoranging(false, true); if (owner_) { // Call order is important, otherwise the lazy event handler won't work owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } } void AnalogSignal::on_conversion_changed(int index) { SignalBase::ConversionType old_conv_type = base_->get_conversion_type(); SignalBase::ConversionType conv_type = (SignalBase::ConversionType)(conversion_cb_->itemData(index).toInt()); if (conv_type != old_conv_type) { base_->set_conversion_type(conv_type); update_conversion_widgets(); if (owner_) owner_->row_item_appearance_changed(false, true); } } void AnalogSignal::on_conv_threshold_changed(int index) { SignalBase::ConversionType conv_type = base_->get_conversion_type(); // Note: index is set to -1 if the text in the combo box matches none of // the entries in the combo box if ((index == -1) && (conv_threshold_cb_->currentText().length() == 0)) return; // The combo box entry with the custom value has user_data set to -1 const int user_data = conv_threshold_cb_->findText( conv_threshold_cb_->currentText()); const bool use_custom_thr = (index == -1) || (user_data == -1); if (conv_type == SignalBase::A2LConversionByThreshold && use_custom_thr) { // Not one of the preset values, try to parse the combo box text // Note: Regex loosely based on // https://txt2re.com/index-c++.php3?s=0.1V&1&-13 QString re1 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value QString re2 = "([a-zA-Z]*)"; // SI unit QRegExp regex(re1 + re2); const QString text = conv_threshold_cb_->currentText(); if (!regex.exactMatch(text)) return; // String doesn't match the regex QStringList tokens = regex.capturedTexts(); // For now, we simply assume that the unit is volt without modifiers const double thr = tokens.at(1).toDouble(); // Only restart the conversion if the threshold was updated. // We're starting a delayed conversion because the user may still be // typing and the UI would lag if we kept on restarting it immediately if (base_->set_conversion_option("threshold_value", thr)) base_->start_conversion(true); } if (conv_type == SignalBase::A2LConversionBySchmittTrigger && use_custom_thr) { // Not one of the preset values, try to parse the combo box text // Note: Regex loosely based on // https://txt2re.com/index-c++.php3?s=0.1V/0.2V&2&14&-22&3&15 QString re1 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value QString re2 = "([a-zA-Z]*)"; // SI unit QString re3 = "\\/"; // Forward slash, not captured QString re4 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value QString re5 = "([a-zA-Z]*)"; // SI unit QRegExp regex(re1 + re2 + re3 + re4 + re5); const QString text = conv_threshold_cb_->currentText(); if (!regex.exactMatch(text)) return; // String doesn't match the regex QStringList tokens = regex.capturedTexts(); // For now, we simply assume that the unit is volt without modifiers const double low_thr = tokens.at(1).toDouble(); const double high_thr = tokens.at(3).toDouble(); // Only restart the conversion if one of the options was updated. // We're starting a delayed conversion because the user may still be // typing and the UI would lag if we kept on restarting it immediately bool o1 = base_->set_conversion_option("threshold_value_low", low_thr); bool o2 = base_->set_conversion_option("threshold_value_high", high_thr); if (o1 || o2) base_->start_conversion(true); // Start delayed conversion } base_->set_conversion_preset((SignalBase::ConversionPreset)index); // Immediately start the conversion if we're not using custom values // (i.e. we're using one of the presets) if (!use_custom_thr) base_->start_conversion(); } void AnalogSignal::on_delayed_conversion_starter() { base_->start_conversion(); } void AnalogSignal::on_display_type_changed(int index) { display_type_ = (DisplayType)(display_type_cb_->itemData(index).toInt()); if (owner_) owner_->row_item_appearance_changed(false, true); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/viewitem.cpp000600 001750 001750 00000005601 13365561737 020430 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "viewitem.hpp" #include #include #include #include namespace pv { namespace views { namespace trace { const QSizeF ViewItem::LabelPadding(4, 0); const int ViewItem::HighlightRadius = 3; ViewItem::ViewItem() : context_parent_(nullptr), drag_point_(INT_MIN, INT_MIN), selected_(false) { } bool ViewItem::is_selectable(QPoint pos) const { (void)pos; return true; } bool ViewItem::selected() const { return selected_; } void ViewItem::select(bool select) { selected_ = select; } bool ViewItem::is_draggable(QPoint pos) const { (void)pos; return true; } bool ViewItem::dragging() const { return drag_point_.x() != INT_MIN && drag_point_.y() != INT_MIN; } void ViewItem::drag() { drag_point_ = drag_point(QRect()); } void ViewItem::drag_release() { drag_point_ = QPoint(INT_MIN, INT_MIN); } QRectF ViewItem::label_rect(const QRectF &rect) const { (void)rect; return QRectF(); } QRectF ViewItem::hit_box_rect(const ViewItemPaintParams &pp) const { (void)pp; return QRectF(); } QMenu* ViewItem::create_header_context_menu(QWidget *parent) { context_parent_ = parent; return new QMenu(parent); } QMenu* ViewItem::create_view_context_menu(QWidget *parent, QPoint &click_pos) { (void)parent; (void)click_pos; return nullptr; } widgets::Popup* ViewItem::create_popup(QWidget *parent) { (void)parent; return nullptr; } void ViewItem::delete_pressed() { } QPen ViewItem::highlight_pen() { return QPen(QApplication::palette().brush( QPalette::Highlight), HighlightRadius * 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); } void ViewItem::paint_label(QPainter &p, const QRect &rect, bool hover) { (void)p; (void)rect; (void)hover; } void ViewItem::paint_back(QPainter &p, ViewItemPaintParams &pp) { (void)p; (void)pp; } void ViewItem::paint_mid(QPainter &p, ViewItemPaintParams &pp) { (void)p; (void)pp; } void ViewItem::paint_fore(QPainter &p, ViewItemPaintParams &pp) { (void)p; (void)pp; } QColor ViewItem::select_text_color(QColor background) { return (background.lightness() > 110) ? Qt::black : Qt::white; } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/viewport.cpp000600 001750 001750 00000014426 13365561737 020463 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include "signal.hpp" #include "view.hpp" #include "viewitempaintparams.hpp" #include "viewport.hpp" #include #include #include using std::abs; using std::back_inserter; using std::copy; using std::dynamic_pointer_cast; using std::none_of; // NOLINT. Used in assert()s. using std::shared_ptr; using std::stable_sort; using std::vector; namespace pv { namespace views { namespace trace { Viewport::Viewport(View &parent) : ViewWidget(parent), pinch_zoom_active_(false) { setAutoFillBackground(true); setBackgroundRole(QPalette::Base); } shared_ptr Viewport::get_mouse_over_item(const QPoint &pt) { const ViewItemPaintParams pp(rect(), view_.scale(), view_.offset()); const vector< shared_ptr > items(this->items()); for (auto i = items.rbegin(); i != items.rend(); i++) if ((*i)->enabled() && (*i)->hit_box_rect(pp).contains(pt)) return *i; return nullptr; } void Viewport::item_hover(const shared_ptr &item, QPoint pos) { if (item && item->is_draggable(pos)) setCursor(dynamic_pointer_cast(item) ? Qt::SizeVerCursor : Qt::SizeHorCursor); else unsetCursor(); } void Viewport::drag() { drag_offset_ = view_.offset(); drag_v_offset_ = view_.owner_visual_v_offset(); } void Viewport::drag_by(const QPoint &delta) { if (drag_offset_ == boost::none) return; view_.set_scale_offset(view_.scale(), (*drag_offset_ - delta.x() * view_.scale())); view_.set_v_offset(-drag_v_offset_ - delta.y()); } void Viewport::drag_release() { drag_offset_ = boost::none; } vector< shared_ptr > Viewport::items() { vector< shared_ptr > items; const vector< shared_ptr > view_items( view_.list_by_type()); copy(view_items.begin(), view_items.end(), back_inserter(items)); const vector< shared_ptr > time_items(view_.time_items()); copy(time_items.begin(), time_items.end(), back_inserter(items)); return items; } bool Viewport::touch_event(QTouchEvent *event) { QList touchPoints = event->touchPoints(); if (touchPoints.count() != 2) { pinch_zoom_active_ = false; return false; } const QTouchEvent::TouchPoint &touchPoint0 = touchPoints.first(); const QTouchEvent::TouchPoint &touchPoint1 = touchPoints.last(); if (!pinch_zoom_active_ || (event->touchPointStates() & Qt::TouchPointPressed)) { pinch_offset0_ = (view_.offset() + view_.scale() * touchPoint0.pos().x()).convert_to(); pinch_offset1_ = (view_.offset() + view_.scale() * touchPoint1.pos().x()).convert_to(); pinch_zoom_active_ = true; } double w = touchPoint1.pos().x() - touchPoint0.pos().x(); if (abs(w) >= 1.0) { const double scale = fabs((pinch_offset1_ - pinch_offset0_) / w); double offset = pinch_offset0_ - touchPoint0.pos().x() * scale; if (scale > 0) view_.set_scale_offset(scale, offset); } if (event->touchPointStates() & Qt::TouchPointReleased) { pinch_zoom_active_ = false; if (touchPoint0.state() & Qt::TouchPointReleased) { // Primary touch released drag_release(); } else { // Update the mouse down fields so that continued // dragging with the primary touch will work correctly mouse_down_point_ = touchPoint0.pos().toPoint(); drag(); } } return true; } void Viewport::paintEvent(QPaintEvent*) { typedef void (ViewItem::*LayerPaintFunc)( QPainter &p, ViewItemPaintParams &pp); LayerPaintFunc layer_paint_funcs[] = { &ViewItem::paint_back, &ViewItem::paint_mid, &ViewItem::paint_fore, nullptr}; vector< shared_ptr > row_items(view_.list_by_type()); assert(none_of(row_items.begin(), row_items.end(), [](const shared_ptr &r) { return !r; })); stable_sort(row_items.begin(), row_items.end(), [](const shared_ptr &a, const shared_ptr &b) { return a->drag_point(QRect()).y() < b->drag_point(QRect()).y(); }); const vector< shared_ptr > time_items(view_.time_items()); assert(none_of(time_items.begin(), time_items.end(), [](const shared_ptr &t) { return !t; })); QPainter p(this); p.setRenderHint(QPainter::Antialiasing); for (LayerPaintFunc *paint_func = layer_paint_funcs; *paint_func; paint_func++) { ViewItemPaintParams time_pp(rect(), view_.scale(), view_.offset()); for (const shared_ptr& t : time_items) (t.get()->*(*paint_func))(p, time_pp); ViewItemPaintParams row_pp(rect(), view_.scale(), view_.offset()); for (const shared_ptr& r : row_items) (r.get()->*(*paint_func))(p, row_pp); } p.end(); } void Viewport::mouseDoubleClickEvent(QMouseEvent *event) { assert(event); if (event->buttons() & Qt::LeftButton) view_.zoom(2.0, event->x()); else if (event->buttons() & Qt::RightButton) view_.zoom(-2.0, event->x()); } void Viewport::wheelEvent(QWheelEvent *event) { assert(event); if (event->orientation() == Qt::Vertical) { if (event->modifiers() & Qt::ControlModifier) { // Vertical scrolling with the control key pressed // is intrepretted as vertical scrolling view_.set_v_offset(-view_.owner_visual_v_offset() - (event->delta() * height()) / (8 * 120)); } else { // Vertical scrolling is interpreted as zooming in/out view_.zoom(event->delta() / 120.0, event->x()); } } else if (event->orientation() == Qt::Horizontal) { // Horizontal scrolling is interpreted as moving left/right view_.set_scale_offset(view_.scale(), event->delta() * view_.scale() + view_.offset()); } } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.1/pv/views/trace/decodetrace.hpp000600 001750 001750 00000015001 13365561737 021041 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_DECODETRACE_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_DECODETRACE_HPP #include "trace.hpp" #include #include #include #include #include #include #include #include #include #include #include #include using std::list; using std::map; using std::pair; using std::shared_ptr; using std::vector; struct srd_channel; struct srd_decoder; namespace pv { class Session; namespace data { struct DecodeChannel; class DecodeSignal; namespace decode { class Decoder; } } // namespace data namespace widgets { class DecoderGroupBox; } namespace views { namespace trace { class DecodeTrace : public Trace { Q_OBJECT private: static const QColor ErrorBgColor; static const QColor NoDecodeColor; static const int ArrowSize; static const double EndCapWidth; static const int RowTitleMargin; static const int DrawPadding; static const int MaxTraceUpdateRate; public: DecodeTrace(pv::Session &session, shared_ptr signalbase, int index); bool enabled() const; shared_ptr base() const; /** * Computes the vertical extents of the contents of this row item. * @return A pair containing the minimum and maximum y-values. */ pair v_extents() const; /** * Paints the background layer of the trace with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with.. */ void paint_back(QPainter &p, ViewItemPaintParams &pp); /** * Paints the mid-layer of the trace with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with. */ void paint_mid(QPainter &p, ViewItemPaintParams &pp); /** * Paints the foreground layer of the trace with a QPainter * @param p the QPainter to paint into. * @param pp the painting parameters object to paint with. */ void paint_fore(QPainter &p, ViewItemPaintParams &pp); void populate_popup_form(QWidget *parent, QFormLayout *form); QMenu* create_header_context_menu(QWidget *parent); virtual QMenu* create_view_context_menu(QWidget *parent, QPoint &click_pos); void delete_pressed(); private: void draw_annotations(vector annotations, QPainter &p, int h, const ViewItemPaintParams &pp, int y, QColor row_color, int row_title_width); void draw_annotation(const pv::data::decode::Annotation &a, QPainter &p, int h, const ViewItemPaintParams &pp, int y, QColor row_color, int row_title_width) const; void draw_annotation_block(qreal start, qreal end, pv::data::decode::Annotation::Class ann_class, bool use_ann_format, QPainter &p, int h, int y, QColor row_color) const; void draw_instant(const pv::data::decode::Annotation &a, QPainter &p, int h, qreal x, int y) const; void draw_range(const pv::data::decode::Annotation &a, QPainter &p, int h, qreal start, qreal end, int y, const ViewItemPaintParams &pp, int row_title_width) const; void draw_error(QPainter &p, const QString &message, const ViewItemPaintParams &pp); void draw_unresolved_period(QPainter &p, int h, int left, int right) const; pair get_pixels_offset_samples_per_pixel() const; /** * Determines the start and end sample for a given pixel range. * @param x_start the X coordinate of the start sample in the view * @param x_end the X coordinate of the end sample in the view * @return Returns a pair containing the start sample and the end * sample that correspond to the start and end coordinates. */ pair get_view_sample_range(int x_start, int x_end) const; QColor get_row_color(int row_index) const; QColor get_annotation_color(QColor row_color, int annotation_index) const; int get_row_at_point(const QPoint &point); const QString get_annotation_at_point(const QPoint &point); void create_decoder_form(int index, shared_ptr &dec, QWidget *parent, QFormLayout *form); QComboBox* create_channel_selector(QWidget *parent, const data::DecodeChannel *ch); QComboBox* create_channel_selector_init_state(QWidget *parent, const data::DecodeChannel *ch); void export_annotations(vector *annotations) const; public: virtual void hover_point_changed(const QPoint &hp); private Q_SLOTS: void on_new_annotations(); void on_delayed_trace_update(); void on_decode_reset(); void on_decode_finished(); void on_pause_decode(); void on_delete(); void on_channel_selected(int); void on_channels_updated(); void on_init_state_changed(int); void on_stack_decoder(srd_decoder *decoder); void on_delete_decoder(int index); void on_show_hide_decoder(int index); void on_export_row(); void on_export_all_rows(); void on_export_row_with_cursor(); void on_export_all_rows_with_cursor(); void on_export_row_from_here(); void on_export_all_rows_from_here(); private: pv::Session &session_; shared_ptr decode_signal_; vector visible_rows_; map channel_id_map_; // channel selector -> decode channel ID map init_state_map_; // init state selector -> decode channel ID list< shared_ptr > bindings_; data::decode::Row *selected_row_; pair selected_sample_range_; vector decoder_forms_; map row_title_widths_; int row_height_, max_visible_rows_; int min_useful_label_width_; QSignalMapper delete_mapper_, show_hide_mapper_; QTimer delayed_trace_updater_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_DECODETRACE_HPP pulseview-0.4.1/pv/views/trace/viewport.hpp000600 001750 001750 00000005251 13365561737 020464 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWPORT_HPP #define PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWPORT_HPP #include #include #include #include #include "pv/util.hpp" #include "viewwidget.hpp" using std::shared_ptr; using std::vector; class QPainter; class QPaintEvent; class Session; namespace pv { namespace views { namespace trace { class View; class Viewport : public ViewWidget { Q_OBJECT public: explicit Viewport(View &parent); /** * Gets the first view item which has a hit-box that contains @c pt . * @param pt the point to search with. * @return the view item that has been found, or and empty * @c shared_ptr if no item was found. */ shared_ptr get_mouse_over_item(const QPoint &pt); private: /** * Indicates when a view item is being hovered over. * @param item The item that is being hovered over, or @c nullptr * if no view item is being hovered over. */ void item_hover(const shared_ptr &item, QPoint pos); /** * Sets this item into the dragged state. */ void drag(); /** * Drag the background by the delta offset. * @param delta the drag offset in pixels. */ void drag_by(const QPoint &delta); /** * Sets this item into the un-dragged state. */ void drag_release(); /** * Gets the items in the view widget. */ vector< shared_ptr > items(); /** * Handles touch begin update and end events. * @param e the event that triggered this handler. */ bool touch_event(QTouchEvent *event); private: void paintEvent(QPaintEvent *event); void mouseDoubleClickEvent(QMouseEvent *event); void wheelEvent(QWheelEvent *event); private: boost::optional drag_offset_; int drag_v_offset_; double pinch_offset0_; double pinch_offset1_; bool pinch_zoom_active_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_VIEWPORT_HPP pulseview-0.4.1/pv/views/trace/standardbar.hpp000600 001750 001750 00000005525 13365561737 021076 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2016 Soeren Apel * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEWS_TRACE_STANDARDBAR_HPP #define PULSEVIEW_PV_VIEWS_TRACE_STANDARDBAR_HPP #include #include #include #include #include #include #include #include "trace.hpp" namespace pv { class MainWindow; class Session; namespace views { namespace trace { class View; } namespace trace { class StandardBar : public QToolBar { Q_OBJECT public: StandardBar(Session &session, QWidget *parent, trace::View *view, bool add_default_widgets = true); Session &session() const; QAction* action_view_zoom_in() const; QAction* action_view_zoom_out() const; QAction* action_view_zoom_fit() const; QAction* action_view_show_cursors() const; protected: virtual void add_toolbar_widgets(); virtual void show_multi_segment_ui(const bool state); Session &session_; trace::View *view_; QAction *const action_view_zoom_in_; QAction *const action_view_zoom_out_; QAction *const action_view_zoom_fit_; QAction *const action_view_show_cursors_; QToolButton *segment_display_mode_selector_; QAction *const action_sdm_last_; QAction *const action_sdm_last_complete_; QAction *const action_sdm_single_; QSpinBox *segment_selector_; Q_SIGNALS: void segment_selected(int segment_id); protected Q_SLOTS: void on_actionViewZoomIn_triggered(); void on_actionViewZoomOut_triggered(); void on_actionViewZoomFit_triggered(bool checked); void on_actionViewShowCursors_triggered(); void on_cursor_state_changed(bool show); void on_actionSDMLast_triggered(); void on_actionSDMLastComplete_triggered(); void on_actionSDMSingle_triggered(); void on_always_zoom_to_fit_changed(bool state); void on_new_segment(int new_segment_id); void on_segment_changed(int segment_id); void on_segment_selected(int ui_segment_id); void on_segment_display_mode_changed(int mode, bool segment_selectable); private: vector multi_segment_actions_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACE_STANDARDBAR_HPP pulseview-0.4.1/pv/devicemanager.cpp000600 001750 001750 00000024175 13365561737 017145 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "devicemanager.hpp" #include "session.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::bind; using std::list; using std::map; using std::placeholders::_1; using std::placeholders::_2; using std::shared_ptr; using std::string; using std::unique_ptr; using std::vector; using Glib::VariantBase; using sigrok::ConfigKey; using sigrok::Context; using sigrok::Driver; namespace pv { DeviceManager::DeviceManager(shared_ptr context, std::string driver, bool do_scan) : context_(context) { unique_ptr progress(new QProgressDialog("", QObject::tr("Cancel"), 0, context->drivers().size() + 1)); progress->setWindowModality(Qt::WindowModal); progress->setMinimumDuration(1); // To show the dialog immediately int entry_num = 1; /* * Check the presence of an optional user spec for device scans. * Determine the driver name and options (in generic format) when * applicable. */ std::string user_name; vector user_opts; if (!driver.empty()) { user_opts = pv::util::split_string(driver, ":"); user_name = user_opts.front(); user_opts.erase(user_opts.begin()); } /* * Scan for devices. No specific options apply here, this is * best effort auto detection. */ for (auto& entry : context->drivers()) { if (!do_scan) break; // Skip drivers we won't scan anyway if (!driver_supported(entry.second)) continue; progress->setLabelText(QObject::tr("Scanning for %1...") .arg(QString::fromStdString(entry.first))); if (entry.first == user_name) continue; driver_scan(entry.second, map()); progress->setValue(entry_num++); QApplication::processEvents(); if (progress->wasCanceled()) break; } /* * Optionally run another scan with potentially more specific * options when requested by the user. This is motivated by * several different uses: It can find devices that are not * covered by the above auto detection (UART, TCP). It can * prefer one out of multiple found devices, and have this * device pre-selected for new sessions upon user's request. */ user_spec_device_.reset(); if (!driver.empty()) { shared_ptr scan_drv; map scan_opts; /* * Lookup the device driver name. */ map> drivers = context->drivers(); auto entry = drivers.find(user_name); scan_drv = (entry != drivers.end()) ? entry->second : nullptr; /* * Convert generic string representation of options * to the driver specific data types. */ if (scan_drv && !user_opts.empty()) { auto drv_opts = scan_drv->scan_options(); scan_opts = drive_scan_options(user_opts, drv_opts); } /* * Run another scan for the specified driver, passing * user provided scan options this time. */ list< shared_ptr > found; if (scan_drv) { found = driver_scan(scan_drv, scan_opts); if (!found.empty()) user_spec_device_ = found.front(); } } progress->setValue(entry_num++); } const shared_ptr& DeviceManager::context() const { return context_; } shared_ptr DeviceManager::context() { return context_; } const list< shared_ptr >& DeviceManager::devices() const { return devices_; } /** * Get the device that was detected with user provided scan options. */ shared_ptr DeviceManager::user_spec_device() const { return user_spec_device_; } /** * Convert generic options to data types that are specific to Driver::scan(). * * @param[in] user_spec Vector of tokenized words, string format. * @param[in] driver_opts Driver's scan options, result of Driver::scan_options(). * * @return Map of options suitable for Driver::scan(). */ map DeviceManager::drive_scan_options(vector user_spec, set driver_opts) { map result; for (auto& entry : user_spec) { /* * Split key=value specs. Accept entries without separator * (for simplified boolean specifications). */ string key, val; size_t pos = entry.find("="); if (pos == std::string::npos) { key = entry; val = ""; } else { key = entry.substr(0, pos); val = entry.substr(pos + 1); } /* * Skip user specifications that are not a member of the * driver's set of supported options. Have the text format * input spec converted to the required driver specific type. */ const ConfigKey *cfg; try { cfg = ConfigKey::get_by_identifier(key); if (!cfg) continue; if (driver_opts.find(cfg) == driver_opts.end()) continue; } catch (...) { continue; } result[cfg] = cfg->parse_string(val); } return result; } bool DeviceManager::driver_supported(shared_ptr driver) const { /* * We currently only support devices that can deliver samples at * a fixed samplerate (i.e. oscilloscopes and logic analysers). * * @todo Add support for non-monotonic devices (DMMs, sensors, etc). */ const auto keys = driver->config_keys(); return keys.count(ConfigKey::LOGIC_ANALYZER) | keys.count(ConfigKey::OSCILLOSCOPE); } list< shared_ptr > DeviceManager::driver_scan( shared_ptr driver, map drvopts) { list< shared_ptr > driver_devices; assert(driver); if (!driver_supported(driver)) return driver_devices; // Remove any device instances from this driver from the device // list. They will not be valid after the scan. devices_.remove_if([&](shared_ptr device) { return device->hardware_device()->driver() == driver; }); try { // Do the scan auto devices = driver->scan(drvopts); // Add the scanned devices to the main list, set display names and sort. for (shared_ptr& device : devices) { const shared_ptr d( new devices::HardwareDevice(context_, device)); driver_devices.push_back(d); } devices_.insert(devices_.end(), driver_devices.begin(), driver_devices.end()); devices_.sort(bind(&DeviceManager::compare_devices, this, _1, _2)); driver_devices.sort(bind( &DeviceManager::compare_devices, this, _1, _2)); } catch (const sigrok::Error &e) { qWarning() << QApplication::tr("Error when scanning device driver '%1': %2"). arg(QString::fromStdString(driver->name()), e.what()); } return driver_devices; } const map DeviceManager::get_device_info( shared_ptr device) { map result; assert(device); const shared_ptr sr_dev = device->device(); if (sr_dev->vendor().length() > 0) result["vendor"] = sr_dev->vendor(); if (sr_dev->model().length() > 0) result["model"] = sr_dev->model(); if (sr_dev->version().length() > 0) result["version"] = sr_dev->version(); if (sr_dev->serial_number().length() > 0) result["serial_num"] = sr_dev->serial_number(); if (sr_dev->connection_id().length() > 0) result["connection_id"] = sr_dev->connection_id(); return result; } const shared_ptr DeviceManager::find_device_from_info( const map search_info) { shared_ptr last_resort_dev; map dev_info; for (shared_ptr dev : devices_) { assert(dev); dev_info = get_device_info(dev); // If present, vendor and model always have to match. if (dev_info.count("vendor") > 0 && search_info.count("vendor") > 0) if (dev_info.at("vendor") != search_info.at("vendor")) continue; if (dev_info.count("model") > 0 && search_info.count("model") > 0) if (dev_info.at("model") != search_info.at("model")) continue; // Most unique match: vendor/model/serial_num (but don't match a S/N of 0) if ((dev_info.count("serial_num") > 0) && (dev_info.at("serial_num") != "0") && search_info.count("serial_num") > 0) if (dev_info.at("serial_num") == search_info.at("serial_num") && dev_info.at("serial_num") != "0") return dev; // Second best match: vendor/model/connection_id if (dev_info.count("connection_id") > 0 && search_info.count("connection_id") > 0) if (dev_info.at("connection_id") == search_info.at("connection_id")) return dev; // Last resort: vendor/model/version if (dev_info.count("version") > 0 && search_info.count("version") > 0) if (dev_info.at("version") == search_info.at("version") && dev_info.at("version") != "0") return dev; // For this device, we merely have a vendor/model match. last_resort_dev = dev; } // If there wasn't even a vendor/model/version match, we end up here. // This is usually the case for devices with only vendor/model data. // The selected device may be wrong with multiple such devices attached // but it is the best we can do at this point. After all, there may be // only one such device and we do want to select it in this case. return last_resort_dev; } bool DeviceManager::compare_devices(shared_ptr a, shared_ptr b) { assert(a); assert(b); return a->display_name(*this).compare(b->display_name(*this)) < 0; } } // namespace pv pulseview-0.4.1/pv/application.cpp000600 001750 001750 00000016145 13365561737 016654 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Martin Ling * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "application.hpp" #include "config.h" #include #include #include #include #ifdef ENABLE_STACKTRACE #include #endif #ifdef ENABLE_DECODE #include #endif using std::cout; using std::endl; using std::exception; using std::shared_ptr; #ifdef ENABLE_DECODE static gint sort_pds(gconstpointer a, gconstpointer b) { const struct srd_decoder *sda, *sdb; sda = (const struct srd_decoder *)a; sdb = (const struct srd_decoder *)b; return strcmp(sda->id, sdb->id); } #endif Application::Application(int &argc, char* argv[]) : QApplication(argc, argv) { setApplicationVersion(PV_VERSION_STRING); setApplicationName("PulseView"); setOrganizationName("sigrok"); setOrganizationDomain("sigrok.org"); } void Application::collect_version_info(shared_ptr context) { // Library versions and features version_info_.emplace_back(applicationName(), applicationVersion()); version_info_.emplace_back("Qt", qVersion()); version_info_.emplace_back("glibmm", PV_GLIBMM_VERSION); version_info_.emplace_back("Boost", BOOST_LIB_VERSION); version_info_.emplace_back("libsigrok", QString("%1/%2 (rt: %3/%4)") .arg(SR_PACKAGE_VERSION_STRING, SR_LIB_VERSION_STRING, sr_package_version_string_get(), sr_lib_version_string_get())); GSList *l_orig = sr_buildinfo_libs_get(); for (GSList *l = l_orig; l; l = l->next) { GSList *m = (GSList *)l->data; const char *lib = (const char *)m->data; const char *version = (const char *)m->next->data; version_info_.emplace_back(QString(" - %1").arg(QString(lib)), QString(version)); g_slist_free_full(m, g_free); } g_slist_free(l_orig); char *host = sr_buildinfo_host_get(); version_info_.emplace_back(" - Host", QString(host)); g_free(host); char *scpi_backends = sr_buildinfo_scpi_backends_get(); version_info_.emplace_back(" - SCPI backends", QString(scpi_backends)); g_free(scpi_backends); #ifdef ENABLE_DECODE struct srd_decoder *dec; version_info_.emplace_back("libsigrokdecode", QString("%1/%2 (rt: %3/%4)") .arg(SRD_PACKAGE_VERSION_STRING, SRD_LIB_VERSION_STRING, srd_package_version_string_get(), srd_lib_version_string_get())); l_orig = srd_buildinfo_libs_get(); for (GSList *l = l_orig; l; l = l->next) { GSList *m = (GSList *)l->data; const char *lib = (const char *)m->data; const char *version = (const char *)m->next->data; version_info_.emplace_back(QString(" - %1").arg(QString(lib)), QString(version)); g_slist_free_full(m, g_free); } g_slist_free(l_orig); host = srd_buildinfo_host_get(); version_info_.emplace_back(" - Host", QString(host)); g_free(host); #endif // Firmware paths l_orig = sr_resourcepaths_get(SR_RESOURCE_FIRMWARE); for (GSList *l = l_orig; l; l = l->next) fw_path_list_.emplace_back((char*)l->data); g_slist_free_full(l_orig, g_free); // PD paths #ifdef ENABLE_DECODE l_orig = srd_searchpaths_get(); for (GSList *l = l_orig; l; l = l->next) pd_path_list_.emplace_back((char*)l->data); g_slist_free_full(l_orig, g_free); #endif // Device drivers for (auto& entry : context->drivers()) driver_list_.emplace_back(QString::fromUtf8(entry.first.c_str()), QString::fromUtf8(entry.second->long_name().c_str())); // Input formats for (auto& entry : context->input_formats()) input_format_list_.emplace_back(QString::fromUtf8(entry.first.c_str()), QString::fromUtf8(entry.second->description().c_str())); // Output formats for (auto& entry : context->output_formats()) output_format_list_.emplace_back(QString::fromUtf8(entry.first.c_str()), QString::fromUtf8(entry.second->description().c_str())); // Protocol decoders #ifdef ENABLE_DECODE GSList *sl = g_slist_copy((GSList *)srd_decoder_list()); sl = g_slist_sort(sl, sort_pds); for (const GSList *l = sl; l; l = l->next) { dec = (struct srd_decoder *)l->data; pd_list_.emplace_back(QString::fromUtf8(dec->id), QString::fromUtf8(dec->longname)); } g_slist_free(sl); #endif } void Application::print_version_info() { cout << PV_TITLE << " " << PV_VERSION_STRING << endl; cout << endl << "Libraries and features:" << endl; for (pair& entry : version_info_) cout << " " << entry.first.toStdString() << " " << entry.second.toStdString() << endl; cout << endl << "Firmware search paths:" << endl; for (QString& entry : fw_path_list_) cout << " " << entry.toStdString() << endl; cout << endl << "Protocol decoder search paths:" << endl; for (QString& entry : pd_path_list_) cout << " " << entry.toStdString() << endl; cout << endl << "Supported hardware drivers:" << endl; for (pair& entry : driver_list_) cout << " " << entry.first.leftJustified(21, ' ').toStdString() << entry.second.toStdString() << endl; cout << endl << "Supported input formats:" << endl; for (pair& entry : input_format_list_) cout << " " << entry.first.leftJustified(21, ' ').toStdString() << entry.second.toStdString() << endl; cout << endl << "Supported output formats:" << endl; for (pair& entry : output_format_list_) cout << " " << entry.first.leftJustified(21, ' ').toStdString() << entry.second.toStdString() << endl; #ifdef ENABLE_DECODE cout << endl << "Supported protocol decoders:" << endl; for (pair& entry : pd_list_) cout << " " << entry.first.leftJustified(21, ' ').toStdString() << entry.second.toStdString() << endl; #endif } vector< pair > Application::get_version_info() const { return version_info_; } vector Application::get_fw_path_list() const { return fw_path_list_; } vector Application::get_pd_path_list() const { return pd_path_list_; } vector< pair > Application::get_driver_list() const { return driver_list_; } vector< pair > Application::get_input_format_list() const { return input_format_list_; } vector< pair > Application::get_output_format_list() const { return output_format_list_; } vector< pair > Application::get_pd_list() const { return pd_list_; } bool Application::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (exception& e) { qDebug().nospace() << "Caught exception of type " << \ typeid(e).name() << " (" << e.what() << ")"; #ifdef ENABLE_STACKTRACE throw e; #else exit(1); #endif return false; } } pulseview-0.4.1/pv/dialogs/000700 001750 001750 00000000000 13365562011 015240 5ustar00uweuwe000000 000000 pulseview-0.4.1/pv/dialogs/settings.cpp000600 001750 001750 00000062146 13365561737 017635 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2017 Soeren Apel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "settings.hpp" #include "pv/application.hpp" #include "pv/devicemanager.hpp" #include "pv/globalsettings.hpp" #include "pv/logging.hpp" #include "pv/widgets/colorbutton.hpp" #include #ifdef ENABLE_DECODE #include #endif using pv::widgets::ColorButton; namespace pv { namespace dialogs { /** * Special version of a QListView that has the width of the first column as minimum size. * * @note Inspired by https://github.com/qt-creator/qt-creator/blob/master/src/plugins/coreplugin/dialogs/settingsdialog.cpp */ class PageListWidget: public QListWidget { public: PageListWidget() : QListWidget() { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); } QSize sizeHint() const final { int width = sizeHintForColumn(0) + frameWidth() * 2 + 5; if (verticalScrollBar()->isVisible()) width += verticalScrollBar()->width(); return QSize(width, 100); } }; Settings::Settings(DeviceManager &device_manager, QWidget *parent) : QDialog(parent, nullptr), device_manager_(device_manager) { resize(600, 400); // Create log view log_view_ = create_log_view(); // Create pages page_list = new PageListWidget(); page_list->setViewMode(QListView::ListMode); page_list->setMovement(QListView::Static); page_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); pages = new QStackedWidget; create_pages(); page_list->setCurrentIndex(page_list->model()->index(0, 0)); // Create the rest of the dialog QHBoxLayout *tab_layout = new QHBoxLayout; tab_layout->addWidget(page_list); tab_layout->addWidget(pages, Qt::AlignLeft); QDialogButtonBox *button_box = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QVBoxLayout* root_layout = new QVBoxLayout(this); root_layout->addLayout(tab_layout); root_layout->addWidget(button_box); connect(button_box, SIGNAL(accepted()), this, SLOT(accept())); connect(button_box, SIGNAL(rejected()), this, SLOT(reject())); connect(page_list, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(on_page_changed(QListWidgetItem*, QListWidgetItem*))); // Start to record changes GlobalSettings settings; settings.start_tracking(); } void Settings::create_pages() { // General page pages->addWidget(get_general_settings_form(pages)); QListWidgetItem *generalButton = new QListWidgetItem(page_list); generalButton->setIcon(QIcon(":/icons/settings-general.png")); generalButton->setText(tr("General")); generalButton->setTextAlignment(Qt::AlignVCenter); generalButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); // View page pages->addWidget(get_view_settings_form(pages)); QListWidgetItem *viewButton = new QListWidgetItem(page_list); viewButton->setIcon(QIcon(":/icons/settings-views.svg")); viewButton->setText(tr("Views")); viewButton->setTextAlignment(Qt::AlignVCenter); viewButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); #ifdef ENABLE_DECODE // Decoder page pages->addWidget(get_decoder_settings_form(pages)); QListWidgetItem *decoderButton = new QListWidgetItem(page_list); decoderButton->setIcon(QIcon(":/icons/add-decoder.svg")); decoderButton->setText(tr("Decoders")); decoderButton->setTextAlignment(Qt::AlignVCenter); decoderButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); #endif // About page pages->addWidget(get_about_page(pages)); QListWidgetItem *aboutButton = new QListWidgetItem(page_list); aboutButton->setIcon(QIcon(":/icons/information.svg")); aboutButton->setText(tr("About")); aboutButton->setTextAlignment(Qt::AlignVCenter); aboutButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); // Logging page pages->addWidget(get_logging_page(pages)); QListWidgetItem *loggingButton = new QListWidgetItem(page_list); loggingButton->setIcon(QIcon(":/icons/information.svg")); loggingButton->setText(tr("Logging")); loggingButton->setTextAlignment(Qt::AlignVCenter); loggingButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } QCheckBox *Settings::create_checkbox(const QString& key, const char* slot) const { GlobalSettings settings; QCheckBox *cb = new QCheckBox(); cb->setChecked(settings.value(key).toBool()); connect(cb, SIGNAL(stateChanged(int)), this, slot); return cb; } QPlainTextEdit *Settings::create_log_view() const { GlobalSettings settings; QPlainTextEdit *log_view = new QPlainTextEdit(); log_view->setReadOnly(true); log_view->setWordWrapMode(QTextOption::NoWrap); log_view->setCenterOnScroll(true); log_view->appendHtml(logging.get_log()); connect(&logging, SIGNAL(logged_text(QString)), log_view, SLOT(appendHtml(QString))); return log_view; } QWidget *Settings::get_general_settings_form(QWidget *parent) const { GlobalSettings settings; QWidget *form = new QWidget(parent); QVBoxLayout *form_layout = new QVBoxLayout(form); // General settings QGroupBox *general_group = new QGroupBox(tr("General")); form_layout->addWidget(general_group); QFormLayout *general_layout = new QFormLayout(); general_group->setLayout(general_layout); QComboBox *theme_cb = new QComboBox(); for (const pair& entry : Themes) theme_cb->addItem(entry.first, entry.second); theme_cb->setCurrentIndex( settings.value(GlobalSettings::Key_General_Theme).toInt()); connect(theme_cb, SIGNAL(currentIndexChanged(int)), this, SLOT(on_general_theme_changed_changed(int))); general_layout->addRow(tr("User interface theme"), theme_cb); QLabel *description_1 = new QLabel(tr("(You may need to restart PulseView for all UI elements to update)")); description_1->setAlignment(Qt::AlignRight); general_layout->addRow(description_1); QComboBox *style_cb = new QComboBox(); style_cb->addItem(tr("System Default"), ""); for (QString& s : QStyleFactory::keys()) style_cb->addItem(s, s); const QString current_style = settings.value(GlobalSettings::Key_General_Style).toString(); if (current_style.isEmpty()) style_cb->setCurrentIndex(0); else style_cb->setCurrentIndex(style_cb->findText(current_style, 0)); connect(style_cb, SIGNAL(currentIndexChanged(int)), this, SLOT(on_general_style_changed(int))); general_layout->addRow(tr("Qt widget style"), style_cb); QLabel *description_2 = new QLabel(tr("(Dark themes look best with the Fusion style)")); description_2->setAlignment(Qt::AlignRight); general_layout->addRow(description_2); return form; } QWidget *Settings::get_view_settings_form(QWidget *parent) const { GlobalSettings settings; QCheckBox *cb; QWidget *form = new QWidget(parent); QVBoxLayout *form_layout = new QVBoxLayout(form); // Trace view settings QGroupBox *trace_view_group = new QGroupBox(tr("Trace View")); form_layout->addWidget(trace_view_group); QFormLayout *trace_view_layout = new QFormLayout(); trace_view_group->setLayout(trace_view_layout); cb = create_checkbox(GlobalSettings::Key_View_ColoredBG, SLOT(on_view_coloredBG_changed(int))); trace_view_layout->addRow(tr("Use colored trace &background"), cb); cb = create_checkbox(GlobalSettings::Key_View_ZoomToFitDuringAcq, SLOT(on_view_zoomToFitDuringAcq_changed(int))); trace_view_layout->addRow(tr("Constantly perform &zoom-to-fit during acquisition"), cb); cb = create_checkbox(GlobalSettings::Key_View_ZoomToFitAfterAcq, SLOT(on_view_zoomToFitAfterAcq_changed(int))); trace_view_layout->addRow(tr("Perform a zoom-to-&fit when acquisition stops"), cb); cb = create_checkbox(GlobalSettings::Key_View_TriggerIsZeroTime, SLOT(on_view_triggerIsZero_changed(int))); trace_view_layout->addRow(tr("Show time zero at the trigger"), cb); cb = create_checkbox(GlobalSettings::Key_View_StickyScrolling, SLOT(on_view_stickyScrolling_changed(int))); trace_view_layout->addRow(tr("Always keep &newest samples at the right edge during capture"), cb); cb = create_checkbox(GlobalSettings::Key_View_ShowSamplingPoints, SLOT(on_view_showSamplingPoints_changed(int))); trace_view_layout->addRow(tr("Show data &sampling points"), cb); cb = create_checkbox(GlobalSettings::Key_View_FillSignalHighAreas, SLOT(on_view_fillSignalHighAreas_changed(int))); trace_view_layout->addRow(tr("Fill high areas of logic signals"), cb); ColorButton* high_fill_cb = new ColorButton(parent); high_fill_cb->set_color(QColor::fromRgba( settings.value(GlobalSettings::Key_View_FillSignalHighAreaColor).value())); connect(high_fill_cb, SIGNAL(selected(QColor)), this, SLOT(on_view_fillSignalHighAreaColor_changed(QColor))); trace_view_layout->addRow(tr("Color to fill high areas of logic signals with"), high_fill_cb); cb = create_checkbox(GlobalSettings::Key_View_ShowAnalogMinorGrid, SLOT(on_view_showAnalogMinorGrid_changed(int))); trace_view_layout->addRow(tr("Show analog minor grid in addition to div grid"), cb); cb = create_checkbox(GlobalSettings::Key_View_ShowHoverMarker, SLOT(on_view_showHoverMarker_changed(int))); trace_view_layout->addRow(tr("Highlight mouse cursor using a vertical marker line"), cb); QSpinBox *snap_distance_sb = new QSpinBox(); snap_distance_sb->setRange(0, 1000); snap_distance_sb->setSuffix(tr(" pixels")); snap_distance_sb->setValue( settings.value(GlobalSettings::Key_View_SnapDistance).toInt()); connect(snap_distance_sb, SIGNAL(valueChanged(int)), this, SLOT(on_view_snapDistance_changed(int))); trace_view_layout->addRow(tr("Maximum distance from edges before cursors snap to them"), snap_distance_sb); ColorButton* cursor_fill_cb = new ColorButton(parent); cursor_fill_cb->set_color(QColor::fromRgba( settings.value(GlobalSettings::Key_View_CursorFillColor).value())); connect(cursor_fill_cb, SIGNAL(selected(QColor)), this, SLOT(on_view_cursorFillColor_changed(QColor))); trace_view_layout->addRow(tr("Color to fill cursor area with"), cursor_fill_cb); QComboBox *thr_disp_mode_cb = new QComboBox(); thr_disp_mode_cb->addItem(tr("None"), GlobalSettings::ConvThrDispMode_None); thr_disp_mode_cb->addItem(tr("Background"), GlobalSettings::ConvThrDispMode_Background); thr_disp_mode_cb->addItem(tr("Dots"), GlobalSettings::ConvThrDispMode_Dots); thr_disp_mode_cb->setCurrentIndex( settings.value(GlobalSettings::Key_View_ConversionThresholdDispMode).toInt()); connect(thr_disp_mode_cb, SIGNAL(currentIndexChanged(int)), this, SLOT(on_view_conversionThresholdDispMode_changed(int))); trace_view_layout->addRow(tr("Conversion threshold display mode (analog traces only)"), thr_disp_mode_cb); QSpinBox *default_div_height_sb = new QSpinBox(); default_div_height_sb->setRange(20, 1000); default_div_height_sb->setSuffix(tr(" pixels")); default_div_height_sb->setValue( settings.value(GlobalSettings::Key_View_DefaultDivHeight).toInt()); connect(default_div_height_sb, SIGNAL(valueChanged(int)), this, SLOT(on_view_defaultDivHeight_changed(int))); trace_view_layout->addRow(tr("Default analog trace div height"), default_div_height_sb); QSpinBox *default_logic_height_sb = new QSpinBox(); default_logic_height_sb->setRange(5, 1000); default_logic_height_sb->setSuffix(tr(" pixels")); default_logic_height_sb->setValue( settings.value(GlobalSettings::Key_View_DefaultLogicHeight).toInt()); connect(default_logic_height_sb, SIGNAL(valueChanged(int)), this, SLOT(on_view_defaultLogicHeight_changed(int))); trace_view_layout->addRow(tr("Default logic trace height"), default_logic_height_sb); return form; } QWidget *Settings::get_decoder_settings_form(QWidget *parent) { #ifdef ENABLE_DECODE GlobalSettings settings; QCheckBox *cb; QWidget *form = new QWidget(parent); QVBoxLayout *form_layout = new QVBoxLayout(form); // Decoder settings QGroupBox *decoder_group = new QGroupBox(tr("Decoders")); form_layout->addWidget(decoder_group); QFormLayout *decoder_layout = new QFormLayout(); decoder_group->setLayout(decoder_layout); cb = create_checkbox(GlobalSettings::Key_Dec_InitialStateConfigurable, SLOT(on_dec_initialStateConfigurable_changed(int))); decoder_layout->addRow(tr("Allow configuration of &initial signal state"), cb); // Annotation export settings ann_export_format_ = new QLineEdit(); ann_export_format_->setText( settings.value(GlobalSettings::Key_Dec_ExportFormat).toString()); connect(ann_export_format_, SIGNAL(textChanged(const QString&)), this, SLOT(on_dec_exportFormat_changed(const QString&))); decoder_layout->addRow(tr("Annotation export format"), ann_export_format_); QLabel *description_1 = new QLabel(tr("%s = sample range; %d: decoder name; %c: row name; %q: use quotations marks")); description_1->setAlignment(Qt::AlignRight); decoder_layout->addRow(description_1); QLabel *description_2 = new QLabel(tr("%1: longest annotation text; %a: all annotation texts")); description_2->setAlignment(Qt::AlignRight); decoder_layout->addRow(description_2); return form; #else (void)parent; return nullptr; #endif } QWidget *Settings::get_about_page(QWidget *parent) const { Application* a = qobject_cast(QApplication::instance()); QLabel *icon = new QLabel(); icon->setPixmap(QPixmap(QString::fromUtf8(":/icons/pulseview.svg"))); // Setup the license field with the project homepage link QLabel *gpl_home_info = new QLabel(); gpl_home_info->setText(tr("%1
%2").arg( tr("GNU GPL, version 3 or later"), QApplication::organizationDomain())); gpl_home_info->setOpenExternalLinks(true); QString s; s.append(""); s.append(""); s.append(""); for (pair &entry : a->get_version_info()) s.append(QString("") .arg(entry.first, entry.second)); s.append(""); s.append(""); for (QString &entry : a->get_fw_path_list()) s.append(QString("").arg(entry)); #ifdef ENABLE_DECODE s.append(""); s.append(""); for (QString &entry : a->get_pd_path_list()) s.append(QString("").arg(entry)); #endif s.append(""); s.append(""); for (pair &entry : a->get_driver_list()) s.append(QString("") .arg(entry.first, entry.second)); s.append(""); s.append(""); for (pair &entry : a->get_input_format_list()) s.append(QString("") .arg(entry.first, entry.second)); s.append(""); s.append(""); for (pair &entry : a->get_output_format_list()) s.append(QString("") .arg(entry.first, entry.second)); #ifdef ENABLE_DECODE s.append(""); s.append(""); for (pair &entry : a->get_pd_list()) s.append(QString("") .arg(entry.first, entry.second)); #endif s.append("
" + tr("Versions, libraries and features:") + "
%1%2
" + tr("Firmware search paths:") + "
%1
" + tr("Protocol decoder search paths:") + "
%1
" + tr("Supported hardware drivers:") + "
%1%2
" + tr("Supported input formats:") + "
%1%2
" + tr("Supported output formats:") + "
%1%2
" + tr("Supported protocol decoders:") + "
%1%2
"); QTextDocument *supported_doc = new QTextDocument(); supported_doc->setHtml(s); QTextBrowser *support_list = new QTextBrowser(); support_list->setDocument(supported_doc); QHBoxLayout *h_layout = new QHBoxLayout(); h_layout->setAlignment(Qt::AlignLeft); h_layout->addWidget(icon); h_layout->addWidget(gpl_home_info); QVBoxLayout *layout = new QVBoxLayout(); layout->addLayout(h_layout); layout->addWidget(support_list); QWidget *page = new QWidget(parent); page->setLayout(layout); return page; } QWidget *Settings::get_logging_page(QWidget *parent) const { GlobalSettings settings; // Log level QSpinBox *loglevel_sb = new QSpinBox(); loglevel_sb->setMaximum(SR_LOG_SPEW); loglevel_sb->setValue(logging.get_log_level()); connect(loglevel_sb, SIGNAL(valueChanged(int)), this, SLOT(on_log_logLevel_changed(int))); QHBoxLayout *loglevel_layout = new QHBoxLayout(); loglevel_layout->addWidget(new QLabel(tr("Log level:"))); loglevel_layout->addWidget(loglevel_sb); // Background buffer size QSpinBox *buffersize_sb = new QSpinBox(); buffersize_sb->setSuffix(tr(" lines")); buffersize_sb->setMinimum(Logging::MIN_BUFFER_SIZE); buffersize_sb->setMaximum(Logging::MAX_BUFFER_SIZE); buffersize_sb->setValue( settings.value(GlobalSettings::Key_Log_BufferSize).toInt()); connect(buffersize_sb, SIGNAL(valueChanged(int)), this, SLOT(on_log_bufferSize_changed(int))); QHBoxLayout *buffersize_layout = new QHBoxLayout(); buffersize_layout->addWidget(new QLabel(tr("Length of background buffer:"))); buffersize_layout->addWidget(buffersize_sb); // Save to file QPushButton *save_log_pb = new QPushButton( QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png")), tr("&Save to File")); connect(save_log_pb, SIGNAL(clicked(bool)), this, SLOT(on_log_saveToFile_clicked(bool))); // Pop out QPushButton *pop_out_pb = new QPushButton( QIcon::fromTheme("window-new", QIcon(":/icons/window-new.png")), tr("&Pop out")); connect(pop_out_pb, SIGNAL(clicked(bool)), this, SLOT(on_log_popOut_clicked(bool))); QHBoxLayout *control_layout = new QHBoxLayout(); control_layout->addLayout(loglevel_layout); control_layout->addLayout(buffersize_layout); control_layout->addWidget(save_log_pb); control_layout->addWidget(pop_out_pb); QVBoxLayout *root_layout = new QVBoxLayout(); root_layout->addLayout(control_layout); root_layout->addWidget(log_view_); QWidget *page = new QWidget(parent); page->setLayout(root_layout); return page; } void Settings::accept() { GlobalSettings settings; settings.stop_tracking(); QDialog::accept(); } void Settings::reject() { GlobalSettings settings; settings.undo_tracked_changes(); QDialog::reject(); } void Settings::on_page_changed(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) current = previous; pages->setCurrentIndex(page_list->row(current)); } void Settings::on_general_theme_changed_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_General_Theme, state); settings.apply_theme(); QMessageBox msg(this); msg.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msg.setIcon(QMessageBox::Question); if (settings.current_theme_is_dark()) { msg.setText(tr("You selected a dark theme.\n" \ "Should I set the user-adjustable colors to better suit your choice?\n\n" \ "Please keep in mind that PulseView may need a restart to display correctly.")); if (msg.exec() == QMessageBox::Yes) settings.set_dark_theme_default_colors(); } else { msg.setText(tr("You selected a bright theme.\n" \ "Should I set the user-adjustable colors to better suit your choice?\n\n" \ "Please keep in mind that PulseView may need a restart to display correctly.")); if (msg.exec() == QMessageBox::Yes) settings.set_bright_theme_default_colors(); } } void Settings::on_general_style_changed(int state) { GlobalSettings settings; if (state == 0) settings.setValue(GlobalSettings::Key_General_Style, ""); else settings.setValue(GlobalSettings::Key_General_Style, QStyleFactory::keys().at(state - 1)); settings.apply_theme(); } void Settings::on_view_zoomToFitDuringAcq_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_ZoomToFitDuringAcq, state ? true : false); } void Settings::on_view_zoomToFitAfterAcq_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_ZoomToFitAfterAcq, state ? true : false); } void Settings::on_view_triggerIsZero_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_TriggerIsZeroTime, state ? true : false); } void Settings::on_view_coloredBG_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_ColoredBG, state ? true : false); } void Settings::on_view_stickyScrolling_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_StickyScrolling, state ? true : false); } void Settings::on_view_showSamplingPoints_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_ShowSamplingPoints, state ? true : false); } void Settings::on_view_fillSignalHighAreas_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_FillSignalHighAreas, state ? true : false); } void Settings::on_view_fillSignalHighAreaColor_changed(QColor color) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_FillSignalHighAreaColor, color.rgba()); } void Settings::on_view_showAnalogMinorGrid_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_ShowAnalogMinorGrid, state ? true : false); } void Settings::on_view_showHoverMarker_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_ShowHoverMarker, state ? true : false); } void Settings::on_view_snapDistance_changed(int value) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_SnapDistance, value); } void Settings::on_view_cursorFillColor_changed(QColor color) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_CursorFillColor, color.rgba()); } void Settings::on_view_conversionThresholdDispMode_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_ConversionThresholdDispMode, state); } void Settings::on_view_defaultDivHeight_changed(int value) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_DefaultDivHeight, value); } void Settings::on_view_defaultLogicHeight_changed(int value) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_DefaultLogicHeight, value); } #ifdef ENABLE_DECODE void Settings::on_dec_initialStateConfigurable_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_Dec_InitialStateConfigurable, state ? true : false); } void Settings::on_dec_exportFormat_changed(const QString &text) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_Dec_ExportFormat, text); } #endif void Settings::on_log_logLevel_changed(int value) { logging.set_log_level(value); } void Settings::on_log_bufferSize_changed(int value) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_Log_BufferSize, value); } void Settings::on_log_saveToFile_clicked(bool checked) { (void)checked; const QString file_name = QFileDialog::getSaveFileName( this, tr("Save Log"), "", tr("Log Files (*.txt *.log);;All Files (*)")); if (file_name.isEmpty()) return; QFile file(file_name); if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { QTextStream out_stream(&file); out_stream << log_view_->toPlainText(); if (out_stream.status() == QTextStream::Ok) { QMessageBox msg(this); msg.setText(tr("Success")); msg.setInformativeText(tr("Log saved to %1.").arg(file_name)); msg.setStandardButtons(QMessageBox::Ok); msg.setIcon(QMessageBox::Information); msg.exec(); return; } } QMessageBox msg(this); msg.setText(tr("Error")); msg.setInformativeText(tr("File %1 could not be written to.").arg(file_name)); msg.setStandardButtons(QMessageBox::Ok); msg.setIcon(QMessageBox::Warning); msg.exec(); } void Settings::on_log_popOut_clicked(bool checked) { (void)checked; // Create the window as a sub-window so it closes when the main window closes QMainWindow *window = new QMainWindow(nullptr, Qt::SubWindow); window->setObjectName(QString::fromUtf8("Log Window")); window->setWindowTitle(tr("%1 Log").arg(PV_TITLE)); // Use same width/height as the settings dialog window->resize(width(), height()); window->setCentralWidget(create_log_view()); window->show(); } } // namespace dialogs } // namespace pv pulseview-0.4.1/pv/dialogs/connect.cpp000600 001750 001750 00000017535 13365561737 017430 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012-2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include "connect.hpp" #include #include using std::list; using std::map; using std::shared_ptr; using std::string; using Glib::ustring; using Glib::Variant; using Glib::VariantBase; using sigrok::ConfigKey; using sigrok::Driver; using pv::devices::HardwareDevice; namespace pv { namespace dialogs { Connect::Connect(QWidget *parent, pv::DeviceManager &device_manager) : QDialog(parent), device_manager_(device_manager), layout_(this), form_(this), form_layout_(&form_), drivers_(&form_), serial_devices_(&form_), scan_button_(tr("&Scan for devices using driver above"), this), device_list_(this), button_box_(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this) { setWindowTitle(tr("Connect to Device")); connect(&button_box_, SIGNAL(accepted()), this, SLOT(accept())); connect(&button_box_, SIGNAL(rejected()), this, SLOT(reject())); populate_drivers(); connect(&drivers_, SIGNAL(activated(int)), this, SLOT(driver_selected(int))); form_.setLayout(&form_layout_); QVBoxLayout *vbox_drv = new QVBoxLayout; vbox_drv->addWidget(&drivers_); QGroupBox *groupbox_drv = new QGroupBox(tr("Step 1: Choose the driver")); groupbox_drv->setLayout(vbox_drv); form_layout_.addRow(groupbox_drv); QRadioButton *radiobtn_usb = new QRadioButton(tr("&USB"), this); QRadioButton *radiobtn_serial = new QRadioButton(tr("Serial &Port"), this); QRadioButton *radiobtn_tcp = new QRadioButton(tr("&TCP/IP"), this); radiobtn_usb->setChecked(true); serial_devices_.setEditable(true); serial_devices_.setEnabled(false); tcp_config_ = new QWidget(); QHBoxLayout *tcp_config_layout = new QHBoxLayout(tcp_config_); tcp_host_ = new QLineEdit; tcp_host_->setText("192.168.1.100"); tcp_config_layout->addWidget(tcp_host_); tcp_config_layout->addWidget(new QLabel(":")); tcp_port_ = new QSpinBox; tcp_port_->setRange(1, 65535); tcp_port_->setValue(5555); tcp_config_layout->addWidget(tcp_port_); tcp_config_layout->addSpacing(30); tcp_config_layout->addWidget(new QLabel(tr("Protocol:"))); tcp_protocol_ = new QComboBox(); tcp_protocol_->addItem("Raw TCP", QVariant("tcp-raw/%1/%2")); tcp_protocol_->addItem("VXI", QVariant("vxi/%1/%2")); tcp_config_layout->addWidget(tcp_protocol_); tcp_config_layout->setContentsMargins(0, 0, 0, 0); tcp_config_->setEnabled(false); // Let the device list occupy only the minimum space needed device_list_.setMaximumHeight(device_list_.minimumSizeHint().height()); QVBoxLayout *vbox_if = new QVBoxLayout; vbox_if->addWidget(radiobtn_usb); vbox_if->addWidget(radiobtn_serial); vbox_if->addWidget(&serial_devices_); vbox_if->addWidget(radiobtn_tcp); vbox_if->addWidget(tcp_config_); QGroupBox *groupbox_if = new QGroupBox(tr("Step 2: Choose the interface")); groupbox_if->setLayout(vbox_if); form_layout_.addRow(groupbox_if); QVBoxLayout *vbox_scan = new QVBoxLayout; vbox_scan->addWidget(&scan_button_); QGroupBox *groupbox_scan = new QGroupBox(tr("Step 3: Scan for devices")); groupbox_scan->setLayout(vbox_scan); form_layout_.addRow(groupbox_scan); QVBoxLayout *vbox_select = new QVBoxLayout; vbox_select->addWidget(&device_list_); QGroupBox *groupbox_select = new QGroupBox(tr("Step 4: Select the device")); groupbox_select->setLayout(vbox_select); form_layout_.addRow(groupbox_select); unset_connection(); connect(radiobtn_serial, SIGNAL(toggled(bool)), this, SLOT(serial_toggled(bool))); connect(radiobtn_tcp, SIGNAL(toggled(bool)), this, SLOT(tcp_toggled(bool))); connect(&scan_button_, SIGNAL(pressed()), this, SLOT(scan_pressed())); setLayout(&layout_); layout_.addWidget(&form_); layout_.addWidget(&button_box_); } shared_ptr Connect::get_selected_device() const { const QListWidgetItem *const item = device_list_.currentItem(); if (!item) return shared_ptr(); return item->data(Qt::UserRole).value>(); } void Connect::populate_drivers() { for (auto& entry : device_manager_.context()->drivers()) { auto name = entry.first; auto driver = entry.second; /** * We currently only support devices that can deliver * samples at a fixed samplerate i.e. oscilloscopes and * logic analysers. * @todo Add support for non-monotonic devices i.e. DMMs * and sensors. */ const auto keys = driver->config_keys(); bool supported_device = keys.count(ConfigKey::LOGIC_ANALYZER) | keys.count(ConfigKey::OSCILLOSCOPE); if (supported_device) drivers_.addItem(QString("%1 (%2)").arg( driver->long_name().c_str(), name.c_str()), qVariantFromValue(driver)); } } void Connect::populate_serials(shared_ptr driver) { serial_devices_.clear(); for (auto& serial : device_manager_.context()->serials(driver)) serial_devices_.addItem(QString("%1 (%2)").arg( serial.first.c_str(), serial.second.c_str()), QString::fromStdString(serial.first)); } void Connect::unset_connection() { device_list_.clear(); button_box_.button(QDialogButtonBox::Ok)->setDisabled(true); } void Connect::serial_toggled(bool checked) { serial_devices_.setEnabled(checked); } void Connect::tcp_toggled(bool checked) { tcp_config_->setEnabled(checked); } void Connect::scan_pressed() { device_list_.clear(); const int index = drivers_.currentIndex(); if (index == -1) return; shared_ptr driver = drivers_.itemData(index).value>(); assert(driver); map drvopts; if (serial_devices_.isEnabled()) { QString serial; const int index = serial_devices_.currentIndex(); if (index >= 0 && index < serial_devices_.count() && serial_devices_.currentText() == serial_devices_.itemText(index)) serial = serial_devices_.itemData(index).toString(); else serial = serial_devices_.currentText(); drvopts[ConfigKey::CONN] = Variant::create( serial.toUtf8().constData()); } if (tcp_config_->isEnabled()) { QString host = tcp_host_->text(); QString port = tcp_port_->text(); if (!host.isEmpty()) { QString conn = tcp_protocol_->itemData(tcp_protocol_->currentIndex()).toString(); conn = conn.arg(host, port); drvopts[ConfigKey::CONN] = Variant::create( conn.toUtf8().constData()); } } const list< shared_ptr > devices = device_manager_.driver_scan(driver, drvopts); for (const shared_ptr& device : devices) { assert(device); QString text = QString::fromStdString(device->display_name(device_manager_)); text += QString(" with %1 channels").arg(device->device()->channels().size()); QListWidgetItem *const item = new QListWidgetItem(text, &device_list_); item->setData(Qt::UserRole, qVariantFromValue(device)); device_list_.addItem(item); } device_list_.setCurrentRow(0); button_box_.button(QDialogButtonBox::Ok)->setDisabled(device_list_.count() == 0); } void Connect::driver_selected(int index) { shared_ptr driver = drivers_.itemData(index).value>(); unset_connection(); populate_serials(driver); } } // namespace dialogs } // namespace pv pulseview-0.4.1/pv/dialogs/storeprogress.cpp000600 001750 001750 00000006302 13365561737 020706 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include "pv/session.hpp" #include "storeprogress.hpp" using std::map; using std::pair; using std::shared_ptr; using std::string; using Glib::VariantBase; namespace pv { namespace dialogs { StoreProgress::StoreProgress(const QString &file_name, const shared_ptr output_format, const map &options, const pair sample_range, const Session &session, QWidget *parent) : QProgressDialog(tr("Saving..."), tr("Cancel"), 0, 0, parent), session_(file_name.toStdString(), output_format, options, sample_range, session) { connect(&session_, SIGNAL(progress_updated()), this, SLOT(on_progress_updated())); connect(&session_, SIGNAL(store_successful()), &session, SLOT(on_data_saved())); // Since we're not setting any progress in case of an error, the dialog // will pop up after the minimumDuration time has been reached - 4000 ms // by default. // We do not want this as it overlaps with the error message box, so we // set the minimumDuration to 0 so that it only appears when we feed it // progress data. Then, call reset() to prevent the progress dialog from // popping up anyway. This would happen in Qt5 because the behavior was // changed in such a way that the duration timer is started by the // constructor. We don't want that and reset() stops the timer, so we // use it. setMinimumDuration(0); reset(); } StoreProgress::~StoreProgress() { session_.wait(); } void StoreProgress::run() { if (session_.start()) show(); else show_error(); } void StoreProgress::show_error() { qDebug() << "Error trying to save:" << session_.error(); QMessageBox msg(parentWidget()); msg.setText(tr("Failed to save session.")); msg.setInformativeText(session_.error()); msg.setStandardButtons(QMessageBox::Ok); msg.setIcon(QMessageBox::Warning); msg.exec(); close(); } void StoreProgress::closeEvent(QCloseEvent*) { session_.cancel(); // Closing doesn't mean we're going to be destroyed because our parent // still owns our handle. Make sure this stale instance doesn't hang around. deleteLater(); } void StoreProgress::on_progress_updated() { const pair p = session_.progress(); assert(p.first <= p.second); if (p.second) { setValue(p.first); setMaximum(p.second); } else { const QString err = session_.error(); if (!err.isEmpty()) show_error(); close(); } } } // namespace dialogs } // namespace pv pulseview-0.4.1/pv/dialogs/inputoutputoptions.cpp000600 001750 001750 00000003356 13365561737 022027 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "inputoutputoptions.hpp" #include using std::map; using std::shared_ptr; using std::string; using Glib::VariantBase; using sigrok::Option; namespace pv { namespace dialogs { InputOutputOptions::InputOutputOptions(const QString &title, const map> &options, QWidget *parent) : QDialog(parent), layout_(this), button_box_(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this), binding_(options) { setWindowTitle(title); connect(&button_box_, SIGNAL(accepted()), this, SLOT(accept())); connect(&button_box_, SIGNAL(rejected()), this, SLOT(reject())); setLayout(&layout_); layout_.addWidget(binding_.get_property_form(this)); layout_.addWidget(&button_box_); } const map& InputOutputOptions::options() const { return binding_.options(); } void InputOutputOptions::accept() { QDialog::accept(); // Commit the properties binding_.commit(); } } // namespace dialogs } // namespace pv pulseview-0.4.1/pv/dialogs/settings.hpp000600 001750 001750 00000006166 13365561737 017642 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2017 Soeren Apel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_SETTINGS_HPP #define PULSEVIEW_PV_SETTINGS_HPP #include #include #include #include #include #include #include namespace pv { class DeviceManager; namespace dialogs { class PageListWidget; class Settings : public QDialog { Q_OBJECT public: Settings(DeviceManager &device_manager, QWidget *parent = nullptr); void create_pages(); QCheckBox *create_checkbox(const QString& key, const char* slot) const; QPlainTextEdit *create_log_view() const; QWidget *get_general_settings_form(QWidget *parent) const; QWidget *get_view_settings_form(QWidget *parent) const; QWidget *get_decoder_settings_form(QWidget *parent); QWidget *get_about_page(QWidget *parent) const; QWidget *get_logging_page(QWidget *parent) const; void accept(); void reject(); private Q_SLOTS: void on_page_changed(QListWidgetItem *current, QListWidgetItem *previous); void on_general_theme_changed_changed(int state); void on_general_style_changed(int state); void on_view_zoomToFitDuringAcq_changed(int state); void on_view_zoomToFitAfterAcq_changed(int state); void on_view_triggerIsZero_changed(int state); void on_view_coloredBG_changed(int state); void on_view_stickyScrolling_changed(int state); void on_view_showSamplingPoints_changed(int state); void on_view_fillSignalHighAreas_changed(int state); void on_view_fillSignalHighAreaColor_changed(QColor color); void on_view_showAnalogMinorGrid_changed(int state); void on_view_showHoverMarker_changed(int state); void on_view_snapDistance_changed(int value); void on_view_cursorFillColor_changed(QColor color); void on_view_conversionThresholdDispMode_changed(int state); void on_view_defaultDivHeight_changed(int value); void on_view_defaultLogicHeight_changed(int value); #ifdef ENABLE_DECODE void on_dec_initialStateConfigurable_changed(int state); void on_dec_exportFormat_changed(const QString &text); #endif void on_log_logLevel_changed(int value); void on_log_bufferSize_changed(int value); void on_log_saveToFile_clicked(bool checked); void on_log_popOut_clicked(bool checked); private: DeviceManager &device_manager_; PageListWidget *page_list; QStackedWidget *pages; #ifdef ENABLE_DECODE QLineEdit *ann_export_format_; #endif QPlainTextEdit *log_view_; }; } // namespace dialogs } // namespace pv #endif // PULSEVIEW_PV_SETTINGS_HPP pulseview-0.4.1/pv/dialogs/inputoutputoptions.hpp000600 001750 001750 00000003536 13365561737 022034 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2015 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DIALOGS_INPUTOUTPUTOPTIONS_HPP #define PULSEVIEW_PV_DIALOGS_INPUTOUTPUTOPTIONS_HPP #include #include #include #include #include using std::map; using std::shared_ptr; using std::string; namespace pv { namespace dialogs { /** * Presents the selection of inpout/output options to the user. */ class InputOutputOptions : public QDialog { Q_OBJECT public: /** * Constructor. * @param title the title of the dialog. * @param options the map of options to use as a template. * @param parent the parent widget of the dialog. */ InputOutputOptions(const QString &title, const map> &options, QWidget *parent); /** * Gets the map of selected options. * @return the options. */ const map& options() const; protected: void accept(); private: QVBoxLayout layout_; QDialogButtonBox button_box_; pv::binding::InputOutput binding_; }; } // namespace dialogs } // namespace pv #endif // PULSEVIEW_PV_INPUTOUTPUTOPTIONS_HPP pulseview-0.4.1/pv/dialogs/storeprogress.hpp000600 001750 001750 00000003226 13365561737 020715 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DIALOGS_SAVEPROGRESS_HPP #define PULSEVIEW_PV_DIALOGS_SAVEPROGRESS_HPP #include #include #include #include using std::map; using std::pair; using std::shared_ptr; using std::string; namespace pv { class Session; namespace dialogs { class StoreProgress : public QProgressDialog { Q_OBJECT public: StoreProgress(const QString &file_name, const shared_ptr output_format, const map &options, const pair sample_range, const Session &session, QWidget *parent = nullptr); virtual ~StoreProgress(); void run(); private: void show_error(); void closeEvent(QCloseEvent*); private Q_SLOTS: void on_progress_updated(); private: pv::StoreSession session_; }; } // namespace dialogs } // namespace pv #endif // PULSEVIEW_PV_DIALOGS_SAVEPROGRESS_HPP pulseview-0.4.1/pv/dialogs/connect.hpp000600 001750 001750 00000004345 13365561737 017430 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012-2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_CONNECT_HPP #define PULSEVIEW_PV_CONNECT_HPP #include #include #include #include #include #include #include #include #include #include #include #include using std::shared_ptr; namespace sigrok { class Driver; } namespace pv { namespace devices { class HardwareDevice; } } Q_DECLARE_METATYPE(shared_ptr); Q_DECLARE_METATYPE(shared_ptr); namespace pv { class DeviceManager; namespace dialogs { class Connect : public QDialog { Q_OBJECT public: Connect(QWidget *parent, pv::DeviceManager &device_manager); shared_ptr get_selected_device() const; private: void populate_drivers(); void populate_serials(shared_ptr driver); void unset_connection(); private Q_SLOTS: void driver_selected(int index); void serial_toggled(bool checked); void tcp_toggled(bool checked); void scan_pressed(); private: pv::DeviceManager &device_manager_; QVBoxLayout layout_; QWidget form_; QFormLayout form_layout_; QComboBox drivers_; QComboBox serial_devices_; QWidget *tcp_config_; QLineEdit *tcp_host_; QSpinBox *tcp_port_; QComboBox *tcp_protocol_; QPushButton scan_button_; QListWidget device_list_; QDialogButtonBox button_box_; }; } // namespace dialogs } // namespace pv #endif // PULSEVIEW_PV_CONNECT_HPP pulseview-0.4.1/pv/data/000700 001750 001750 00000000000 13365562011 014527 5ustar00uweuwe000000 000000 pulseview-0.4.1/pv/data/signalbase.cpp000600 001750 001750 00000052157 13365561737 017375 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * Copyright (C) 2016 Soeren Apel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "analog.hpp" #include "analogsegment.hpp" #include "decode/row.hpp" #include "logic.hpp" #include "logicsegment.hpp" #include "signalbase.hpp" #include "signaldata.hpp" #include #include #include using std::dynamic_pointer_cast; using std::make_shared; using std::out_of_range; using std::shared_ptr; using std::tie; using std::unique_lock; namespace pv { namespace data { const int SignalBase::ColorBGAlpha = 8 * 256 / 100; const uint64_t SignalBase::ConversionBlockSize = 4096; const uint32_t SignalBase::ConversionDelay = 1000; // 1 second SignalBase::SignalBase(shared_ptr channel, ChannelType channel_type) : channel_(channel), channel_type_(channel_type), conversion_type_(NoConversion), min_value_(0), max_value_(0) { if (channel_) internal_name_ = QString::fromStdString(channel_->name()); connect(&delayed_conversion_starter_, SIGNAL(timeout()), this, SLOT(on_delayed_conversion_start())); delayed_conversion_starter_.setSingleShot(true); delayed_conversion_starter_.setInterval(ConversionDelay); } SignalBase::~SignalBase() { stop_conversion(); } shared_ptr SignalBase::channel() const { return channel_; } QString SignalBase::name() const { return (channel_) ? QString::fromStdString(channel_->name()) : name_; } QString SignalBase::internal_name() const { return internal_name_; } QString SignalBase::display_name() const { if (name() != internal_name_) return name() + " (" + internal_name_ + ")"; else return name(); } void SignalBase::set_name(QString name) { if (channel_) channel_->set_name(name.toUtf8().constData()); name_ = name; name_changed(name); } bool SignalBase::enabled() const { return (channel_) ? channel_->enabled() : true; } void SignalBase::set_enabled(bool value) { if (channel_) { channel_->set_enabled(value); enabled_changed(value); } } SignalBase::ChannelType SignalBase::type() const { return channel_type_; } unsigned int SignalBase::index() const { return (channel_) ? channel_->index() : 0; } unsigned int SignalBase::logic_bit_index() const { if (channel_type_ == LogicChannel) return channel_->index(); else return 0; } QColor SignalBase::color() const { return color_; } void SignalBase::set_color(QColor color) { color_ = color; bgcolor_ = color; bgcolor_.setAlpha(ColorBGAlpha); color_changed(color); } QColor SignalBase::bgcolor() const { return bgcolor_; } void SignalBase::set_data(shared_ptr data) { if (data_) { disconnect(data.get(), SIGNAL(samples_cleared()), this, SLOT(on_samples_cleared())); disconnect(data.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)), this, SLOT(on_samples_added(QObject*, uint64_t, uint64_t))); if (channel_type_ == AnalogChannel) { shared_ptr analog = analog_data(); assert(analog); disconnect(analog.get(), SIGNAL(min_max_changed(float, float)), this, SLOT(on_min_max_changed(float, float))); } } data_ = data; if (data_) { connect(data.get(), SIGNAL(samples_cleared()), this, SLOT(on_samples_cleared())); connect(data.get(), SIGNAL(samples_added(QObject*, uint64_t, uint64_t)), this, SLOT(on_samples_added(QObject*, uint64_t, uint64_t))); if (channel_type_ == AnalogChannel) { shared_ptr analog = analog_data(); assert(analog); connect(analog.get(), SIGNAL(min_max_changed(float, float)), this, SLOT(on_min_max_changed(float, float))); } } } shared_ptr SignalBase::analog_data() const { shared_ptr result = nullptr; if (channel_type_ == AnalogChannel) result = dynamic_pointer_cast(data_); return result; } shared_ptr SignalBase::logic_data() const { shared_ptr result = nullptr; if (channel_type_ == LogicChannel) result = dynamic_pointer_cast(data_); if (((conversion_type_ == A2LConversionByThreshold) || (conversion_type_ == A2LConversionBySchmittTrigger))) result = dynamic_pointer_cast(converted_data_); return result; } bool SignalBase::segment_is_complete(uint32_t segment_id) const { bool result = true; if (channel_type_ == AnalogChannel) { shared_ptr data = dynamic_pointer_cast(data_); auto segments = data->analog_segments(); try { result = segments.at(segment_id)->is_complete(); } catch (out_of_range&) { // Do nothing } } if (channel_type_ == LogicChannel) { shared_ptr data = dynamic_pointer_cast(data_); auto segments = data->logic_segments(); try { result = segments.at(segment_id)->is_complete(); } catch (out_of_range&) { // Do nothing } } return result; } bool SignalBase::has_samples() const { bool result = false; if (channel_type_ == AnalogChannel) { shared_ptr data = dynamic_pointer_cast(data_); if (data) { auto segments = data->analog_segments(); if ((segments.size() > 0) && (segments.front()->get_sample_count() > 0)) result = true; } } if (channel_type_ == LogicChannel) { shared_ptr data = dynamic_pointer_cast(data_); if (data) { auto segments = data->logic_segments(); if ((segments.size() > 0) && (segments.front()->get_sample_count() > 0)) result = true; } } 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_; } void SignalBase::set_conversion_type(ConversionType t) { if (conversion_type_ != NoConversion) { stop_conversion(); // Discard converted data converted_data_.reset(); samples_cleared(); } conversion_type_ = t; // Re-create an empty container // so that the signal is recognized as providing logic data // and thus can be assigned to a decoder if (conversion_is_a2l()) if (!converted_data_) converted_data_ = make_shared(1); // Contains only one channel start_conversion(); conversion_type_changed(t); } map SignalBase::get_conversion_options() const { return conversion_options_; } bool SignalBase::set_conversion_option(QString key, QVariant value) { QVariant old_value; auto key_iter = conversion_options_.find(key); if (key_iter != conversion_options_.end()) old_value = key_iter->second; conversion_options_[key] = value; return (value != old_value); } vector SignalBase::get_conversion_thresholds(const ConversionType t, const bool always_custom) const { vector result; ConversionType conv_type = t; ConversionPreset preset; // Use currently active conversion if no conversion type was supplied if (conv_type == NoConversion) conv_type = conversion_type_; if (always_custom) preset = NoPreset; else preset = get_current_conversion_preset(); if (conv_type == A2LConversionByThreshold) { double thr = 0; if (preset == NoPreset) { auto thr_iter = conversion_options_.find("threshold_value"); if (thr_iter != conversion_options_.end()) thr = (thr_iter->second).toDouble(); } if (preset == DynamicPreset) thr = (min_value_ + max_value_) * 0.5; // middle between min and max if ((int)preset == 1) thr = 0.9; if ((int)preset == 2) thr = 1.8; if ((int)preset == 3) thr = 2.5; if ((int)preset == 4) thr = 1.5; result.push_back(thr); } if (conv_type == A2LConversionBySchmittTrigger) { double thr_lo = 0, thr_hi = 0; if (preset == NoPreset) { auto thr_lo_iter = conversion_options_.find("threshold_value_low"); if (thr_lo_iter != conversion_options_.end()) thr_lo = (thr_lo_iter->second).toDouble(); auto thr_hi_iter = conversion_options_.find("threshold_value_high"); if (thr_hi_iter != conversion_options_.end()) thr_hi = (thr_hi_iter->second).toDouble(); } if (preset == DynamicPreset) { const double amplitude = max_value_ - min_value_; const double center = min_value_ + (amplitude / 2); thr_lo = center - (amplitude * 0.15); // 15% margin thr_hi = center + (amplitude * 0.15); // 15% margin } if ((int)preset == 1) { thr_lo = 0.3; thr_hi = 1.2; } if ((int)preset == 2) { thr_lo = 0.7; thr_hi = 2.5; } if ((int)preset == 3) { thr_lo = 1.3; thr_hi = 3.7; } if ((int)preset == 4) { thr_lo = 0.8; thr_hi = 2.0; } result.push_back(thr_lo); result.push_back(thr_hi); } return result; } vector< pair > SignalBase::get_conversion_presets() const { vector< pair > presets; if (conversion_type_ == A2LConversionByThreshold) { // Source: http://www.interfacebus.com/voltage_threshold.html presets.emplace_back(tr("Signal average"), 0); presets.emplace_back(tr("0.9V (for 1.8V CMOS)"), 1); presets.emplace_back(tr("1.8V (for 3.3V CMOS)"), 2); presets.emplace_back(tr("2.5V (for 5.0V CMOS)"), 3); presets.emplace_back(tr("1.5V (for TTL)"), 4); } if (conversion_type_ == A2LConversionBySchmittTrigger) { // Source: http://www.interfacebus.com/voltage_threshold.html presets.emplace_back(tr("Signal average +/- 15%"), 0); presets.emplace_back(tr("0.3V/1.2V (for 1.8V CMOS)"), 1); presets.emplace_back(tr("0.7V/2.5V (for 3.3V CMOS)"), 2); presets.emplace_back(tr("1.3V/3.7V (for 5.0V CMOS)"), 3); presets.emplace_back(tr("0.8V/2.0V (for TTL)"), 4); } return presets; } SignalBase::ConversionPreset SignalBase::get_current_conversion_preset() const { auto preset = conversion_options_.find("preset"); if (preset != conversion_options_.end()) return (ConversionPreset)((preset->second).toInt()); return DynamicPreset; } void SignalBase::set_conversion_preset(ConversionPreset id) { conversion_options_["preset"] = (int)id; } #ifdef ENABLE_DECODE bool SignalBase::is_decode_signal() const { return (channel_type_ == DecodeChannel); } #endif void SignalBase::save_settings(QSettings &settings) const { settings.setValue("name", name()); settings.setValue("enabled", enabled()); settings.setValue("color", color().rgba()); settings.setValue("conversion_type", (int)conversion_type_); settings.setValue("conv_options", (int)(conversion_options_.size())); int i = 0; for (auto& kvp : conversion_options_) { settings.setValue(QString("conv_option%1_key").arg(i), kvp.first); settings.setValue(QString("conv_option%1_value").arg(i), kvp.second); i++; } } void SignalBase::restore_settings(QSettings &settings) { if (settings.contains("name")) set_name(settings.value("name").toString()); if (settings.contains("enabled")) set_enabled(settings.value("enabled").toBool()); if (settings.contains("color")) { QVariant value = settings.value("color"); // Workaround for Qt QColor serialization bug on OSX if ((QMetaType::Type)(value.type()) == QMetaType::QColor) set_color(value.value()); else set_color(QColor::fromRgba(value.value())); // A color with an alpha value of 0 makes the signal marker invisible if (color() == QColor(0, 0, 0, 0)) set_color(Qt::gray); } if (settings.contains("conversion_type")) set_conversion_type((ConversionType)settings.value("conversion_type").toInt()); int conv_options = 0; if (settings.contains("conv_options")) conv_options = settings.value("conv_options").toInt(); if (conv_options) for (int i = 0; i < conv_options; i++) { const QString key_id = QString("conv_option%1_key").arg(i); const QString value_id = QString("conv_option%1_value").arg(i); if (settings.contains(key_id) && settings.contains(value_id)) conversion_options_[settings.value(key_id).toString()] = settings.value(value_id); } } bool SignalBase::conversion_is_a2l() const { return ((channel_type_ == AnalogChannel) && ((conversion_type_ == A2LConversionByThreshold) || (conversion_type_ == A2LConversionBySchmittTrigger))); } void SignalBase::convert_single_segment_range(AnalogSegment *asegment, LogicSegment *lsegment, uint64_t start_sample, uint64_t end_sample) { if (end_sample > start_sample) { tie(min_value_, max_value_) = asegment->get_min_max(); // Create sigrok::Analog instance float *asamples = new float[ConversionBlockSize]; uint8_t *lsamples = new uint8_t[ConversionBlockSize]; vector > channels; channels.push_back(channel_); vector mq_flags; const sigrok::Quantity * const mq = sigrok::Quantity::VOLTAGE; const sigrok::Unit * const unit = sigrok::Unit::VOLT; shared_ptr packet = Session::sr_context->create_analog_packet(channels, asamples, ConversionBlockSize, mq, unit, mq_flags); shared_ptr analog = dynamic_pointer_cast(packet->payload()); // Convert uint64_t i = start_sample; if (conversion_type_ == A2LConversionByThreshold) { const double threshold = get_conversion_thresholds()[0]; // Convert as many sample blocks as we can while ((end_sample - i) > ConversionBlockSize) { asegment->get_samples(i, i + ConversionBlockSize, asamples); shared_ptr logic = analog->get_logic_via_threshold(threshold, lsamples); lsegment->append_payload(logic->data_pointer(), logic->data_length()); samples_added(lsegment->segment_id(), i, i + ConversionBlockSize); i += ConversionBlockSize; } // Re-create sigrok::Analog and convert remaining samples packet = Session::sr_context->create_analog_packet(channels, asamples, end_sample - i, mq, unit, mq_flags); analog = dynamic_pointer_cast(packet->payload()); asegment->get_samples(i, end_sample, asamples); shared_ptr logic = analog->get_logic_via_threshold(threshold, lsamples); lsegment->append_payload(logic->data_pointer(), logic->data_length()); samples_added(lsegment->segment_id(), i, end_sample); } if (conversion_type_ == A2LConversionBySchmittTrigger) { const vector thresholds = get_conversion_thresholds(); const double lo_thr = thresholds[0]; const double hi_thr = thresholds[1]; uint8_t state = 0; // TODO Use value of logic sample n-1 instead of 0 // Convert as many sample blocks as we can while ((end_sample - i) > ConversionBlockSize) { asegment->get_samples(i, i + ConversionBlockSize, asamples); shared_ptr logic = analog->get_logic_via_schmitt_trigger(lo_thr, hi_thr, &state, lsamples); lsegment->append_payload(logic->data_pointer(), logic->data_length()); samples_added(lsegment->segment_id(), i, i + ConversionBlockSize); i += ConversionBlockSize; } // Re-create sigrok::Analog and convert remaining samples packet = Session::sr_context->create_analog_packet(channels, asamples, end_sample - i, mq, unit, mq_flags); analog = dynamic_pointer_cast(packet->payload()); asegment->get_samples(i, end_sample, asamples); shared_ptr logic = analog->get_logic_via_schmitt_trigger(lo_thr, hi_thr, &state, lsamples); lsegment->append_payload(logic->data_pointer(), logic->data_length()); samples_added(lsegment->segment_id(), i, end_sample); } // If acquisition is ongoing, start-/endsample may have changed end_sample = asegment->get_sample_count(); delete[] lsamples; delete[] asamples; } } void SignalBase::convert_single_segment(AnalogSegment *asegment, LogicSegment *lsegment) { uint64_t start_sample, end_sample, old_end_sample; start_sample = end_sample = 0; bool complete_state, old_complete_state; start_sample = lsegment->get_sample_count(); end_sample = asegment->get_sample_count(); complete_state = asegment->is_complete(); // Don't do anything if the segment is still being filled and the sample count is too small if ((!complete_state) && (end_sample - start_sample < ConversionBlockSize)) return; do { convert_single_segment_range(asegment, lsegment, start_sample, end_sample); old_end_sample = end_sample; old_complete_state = complete_state; start_sample = lsegment->get_sample_count(); end_sample = asegment->get_sample_count(); complete_state = asegment->is_complete(); // If the segment has been incomplete when we were called and has been // completed in the meanwhile, we convert the remaining samples as well. // Also, if a sufficient number of samples was added in the meanwhile, // we do another round of sample conversion. } while ((complete_state != old_complete_state) || (end_sample - old_end_sample >= ConversionBlockSize)); } void SignalBase::conversion_thread_proc() { shared_ptr analog_data; if (conversion_is_a2l()) { analog_data = dynamic_pointer_cast(data_); if (analog_data->analog_segments().size() == 0) { unique_lock input_lock(conversion_input_mutex_); conversion_input_cond_.wait(input_lock); } } else // Currently, we only handle A2L conversions return; // If we had to wait for input data, we may have been notified to terminate if (conversion_interrupt_) return; uint32_t segment_id = 0; AnalogSegment *asegment = analog_data->analog_segments().front().get(); assert(asegment); const shared_ptr logic_data = dynamic_pointer_cast(converted_data_); assert(logic_data); // Create the initial logic data segment if needed if (logic_data->logic_segments().size() == 0) { shared_ptr new_segment = make_shared(*logic_data.get(), 0, 1, asegment->samplerate()); logic_data->push_segment(new_segment); } LogicSegment *lsegment = logic_data->logic_segments().front().get(); assert(lsegment); do { convert_single_segment(asegment, lsegment); // Only advance to next segment if the current input segment is complete if (asegment->is_complete() && analog_data->analog_segments().size() > logic_data->logic_segments().size()) { // There are more segments to process segment_id++; try { asegment = analog_data->analog_segments().at(segment_id).get(); } catch (out_of_range&) { qDebug() << "Conversion error for" << name() << ": no analog segment" \ << segment_id << ", segments size is" << analog_data->analog_segments().size(); return; } shared_ptr new_segment = make_shared( *logic_data.get(), segment_id, 1, asegment->samplerate()); logic_data->push_segment(new_segment); lsegment = logic_data->logic_segments().back().get(); } else { // No more samples/segments to process, wait for data or interrupt if (!conversion_interrupt_) { unique_lock input_lock(conversion_input_mutex_); conversion_input_cond_.wait(input_lock); } } } while (!conversion_interrupt_); } void SignalBase::start_conversion(bool delayed_start) { if (delayed_start) { delayed_conversion_starter_.start(); return; } stop_conversion(); if (converted_data_) converted_data_->clear(); samples_cleared(); conversion_interrupt_ = false; conversion_thread_ = std::thread( &SignalBase::conversion_thread_proc, this); } void SignalBase::stop_conversion() { // Stop conversion so we can restart it from the beginning conversion_interrupt_ = true; conversion_input_cond_.notify_one(); if (conversion_thread_.joinable()) conversion_thread_.join(); } void SignalBase::on_samples_cleared() { if (converted_data_) converted_data_->clear(); samples_cleared(); } void SignalBase::on_samples_added(QObject* segment, uint64_t start_sample, uint64_t end_sample) { if (conversion_type_ != NoConversion) { if (conversion_thread_.joinable()) { // Notify the conversion thread since it's running conversion_input_cond_.notify_one(); } else { // Start the conversion thread unless the delay timer is running if (!delayed_conversion_starter_.isActive()) start_conversion(); } } data::Segment* s = qobject_cast(segment); samples_added(s->segment_id(), start_sample, end_sample); } void SignalBase::on_min_max_changed(float min, float max) { // Restart conversion if one is enabled and uses a calculated threshold if ((conversion_type_ != NoConversion) && (get_current_conversion_preset() == DynamicPreset)) start_conversion(true); min_max_changed(min, max); } void SignalBase::on_capture_state_changed(int state) { if (state == Session::Running) { // Restart conversion if one is enabled if (conversion_type_ != NoConversion) start_conversion(); } } void SignalBase::on_delayed_conversion_start() { start_conversion(); } } // namespace data } // namespace pv pulseview-0.4.1/pv/data/decodesignal.cpp000600 001750 001750 00000110114 13365561737 017672 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2017 Soeren Apel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include "logic.hpp" #include "logicsegment.hpp" #include "decodesignal.hpp" #include "signaldata.hpp" #include #include #include #include #include using std::forward_list; using std::lock_guard; using std::make_pair; using std::make_shared; using std::min; using std::out_of_range; using std::shared_ptr; using std::unique_lock; using pv::data::decode::Annotation; using pv::data::decode::Decoder; using pv::data::decode::Row; namespace pv { namespace data { const double DecodeSignal::DecodeMargin = 1.0; const double DecodeSignal::DecodeThreshold = 0.2; const int64_t DecodeSignal::DecodeChunkLength = 256 * 1024; DecodeSignal::DecodeSignal(pv::Session &session) : SignalBase(nullptr, SignalBase::DecodeChannel), session_(session), srd_session_(nullptr), logic_mux_data_invalid_(false), stack_config_changed_(true), current_segment_id_(0) { connect(&session_, SIGNAL(capture_state_changed(int)), this, SLOT(on_capture_state_changed(int))); } DecodeSignal::~DecodeSignal() { reset_decode(true); } const vector< shared_ptr >& DecodeSignal::decoder_stack() const { return stack_; } void DecodeSignal::stack_decoder(const srd_decoder *decoder) { assert(decoder); const shared_ptr dec = make_shared(decoder); stack_.push_back(dec); // Set name if this decoder is the first in the list if (stack_.size() == 1) set_name(QString::fromUtf8(decoder->name)); // Include the newly created decode channels in the channel lists update_channel_list(); stack_config_changed_ = true; auto_assign_signals(dec); commit_decoder_channels(); begin_decode(); } void DecodeSignal::remove_decoder(int index) { assert(index >= 0); assert(index < (int)stack_.size()); // Find the decoder in the stack auto iter = stack_.begin(); for (int i = 0; i < index; i++, iter++) assert(iter != stack_.end()); // Delete the element stack_.erase(iter); // Update channels and decoded data stack_config_changed_ = true; update_channel_list(); begin_decode(); } bool DecodeSignal::toggle_decoder_visibility(int index) { auto iter = stack_.cbegin(); for (int i = 0; i < index; i++, iter++) assert(iter != stack_.end()); shared_ptr dec = *iter; // Toggle decoder visibility bool state = false; if (dec) { state = !dec->shown(); dec->show(state); } return state; } void DecodeSignal::reset_decode(bool shutting_down) { if (stack_config_changed_ || shutting_down) stop_srd_session(); else terminate_srd_session(); if (decode_thread_.joinable()) { decode_interrupt_ = true; decode_input_cond_.notify_one(); decode_thread_.join(); } if (logic_mux_thread_.joinable()) { logic_mux_interrupt_ = true; logic_mux_cond_.notify_one(); logic_mux_thread_.join(); } resume_decode(); // Make sure the decode thread isn't blocked by pausing class_rows_.clear(); current_segment_id_ = 0; segments_.clear(); logic_mux_data_.reset(); logic_mux_data_invalid_ = true; if (!error_message_.isEmpty()) { error_message_ = QString(); // TODO Emulate noquote() qDebug().nospace() << name() << ": Error cleared"; } decode_reset(); } void DecodeSignal::begin_decode() { if (decode_thread_.joinable()) { decode_interrupt_ = true; decode_input_cond_.notify_one(); decode_thread_.join(); } if (logic_mux_thread_.joinable()) { logic_mux_interrupt_ = true; logic_mux_cond_.notify_one(); logic_mux_thread_.join(); } reset_decode(); if (stack_.size() == 0) { set_error_message(tr("No decoders")); return; } assert(channels_.size() > 0); if (get_assigned_signal_count() == 0) { set_error_message(tr("There are no channels assigned to this decoder")); return; } // Make sure that all assigned channels still provide logic data // (can happen when a converted signal was assigned but the // conversion removed in the meanwhile) for (data::DecodeChannel& ch : channels_) if (ch.assigned_signal && !(ch.assigned_signal->logic_data() != nullptr)) ch.assigned_signal = nullptr; // Check that all decoders have the required channels for (const shared_ptr& dec : stack_) if (!dec->have_required_channels()) { set_error_message(tr("One or more required channels " "have not been specified")); return; } // Map out all the annotation classes int row_index = 0; for (const shared_ptr& dec : stack_) { assert(dec); const srd_decoder *const decc = dec->decoder(); assert(dec->decoder()); for (const GSList *l = decc->annotation_rows; l; l = l->next) { const srd_decoder_annotation_row *const ann_row = (srd_decoder_annotation_row *)l->data; assert(ann_row); const Row row(row_index++, decc, ann_row); for (const GSList *ll = ann_row->ann_classes; ll; ll = ll->next) class_rows_[make_pair(decc, GPOINTER_TO_INT(ll->data))] = row; } } // Free the logic data and its segment(s) if it needs to be updated if (logic_mux_data_invalid_) logic_mux_data_.reset(); if (!logic_mux_data_) { const uint32_t ch_count = get_assigned_signal_count(); logic_mux_unit_size_ = (ch_count + 7) / 8; logic_mux_data_ = make_shared(ch_count); } // Receive notifications when new sample data is available connect_input_notifiers(); if (get_input_segment_count() == 0) { set_error_message(tr("No input data")); return; } // Make sure the logic output data is complete and up-to-date logic_mux_interrupt_ = false; logic_mux_thread_ = std::thread(&DecodeSignal::logic_mux_proc, this); // Decode the muxed logic data decode_interrupt_ = false; decode_thread_ = std::thread(&DecodeSignal::decode_proc, this); } void DecodeSignal::pause_decode() { decode_paused_ = true; } void DecodeSignal::resume_decode() { // Manual unlocking is done before notifying, to avoid waking up the // waiting thread only to block again (see notify_one for details) decode_pause_mutex_.unlock(); decode_pause_cond_.notify_one(); decode_paused_ = false; } bool DecodeSignal::is_paused() const { return decode_paused_; } QString DecodeSignal::error_message() const { lock_guard lock(output_mutex_); return error_message_; } const vector DecodeSignal::get_channels() const { return channels_; } void DecodeSignal::auto_assign_signals(const shared_ptr dec) { bool new_assignment = false; // Try to auto-select channels that don't have signals assigned yet for (data::DecodeChannel& ch : channels_) { // If a decoder is given, auto-assign only its channels if (dec && (ch.decoder_ != dec)) continue; if (ch.assigned_signal) continue; QString ch_name = ch.name.toLower(); ch_name = ch_name.replace(QRegExp("[-_.]"), " "); shared_ptr match; for (const shared_ptr& s : session_.signalbases()) { if (!s->enabled()) continue; QString s_name = s->name().toLower(); s_name = s_name.replace(QRegExp("[-_.]"), " "); if (s->logic_data() && ((ch_name.contains(s_name)) || (s_name.contains(ch_name)))) { if (!match) match = s; else { // Only replace an existing match if it matches more characters int old_unmatched = ch_name.length() - match->name().length(); int new_unmatched = ch_name.length() - s->name().length(); if (abs(new_unmatched) < abs(old_unmatched)) match = s; } } } if (match) { ch.assigned_signal = match.get(); new_assignment = true; } } if (new_assignment) { logic_mux_data_invalid_ = true; stack_config_changed_ = true; commit_decoder_channels(); channels_updated(); } } void DecodeSignal::assign_signal(const uint16_t channel_id, const SignalBase *signal) { for (data::DecodeChannel& ch : channels_) if (ch.id == channel_id) { ch.assigned_signal = signal; logic_mux_data_invalid_ = true; } stack_config_changed_ = true; commit_decoder_channels(); channels_updated(); begin_decode(); } int DecodeSignal::get_assigned_signal_count() const { // Count all channels that have a signal assigned to them return count_if(channels_.begin(), channels_.end(), [](data::DecodeChannel ch) { return ch.assigned_signal; }); } void DecodeSignal::set_initial_pin_state(const uint16_t channel_id, const int init_state) { for (data::DecodeChannel& ch : channels_) if (ch.id == channel_id) ch.initial_pin_state = init_state; stack_config_changed_ = true; channels_updated(); begin_decode(); } double DecodeSignal::samplerate() const { double result = 0; // TODO For now, we simply return the first samplerate that we have if (segments_.size() > 0) result = segments_.front().samplerate; return result; } const pv::util::Timestamp DecodeSignal::start_time() const { pv::util::Timestamp result; // TODO For now, we simply return the first start time that we have if (segments_.size() > 0) result = segments_.front().start_time; return result; } int64_t DecodeSignal::get_working_sample_count(uint32_t segment_id) const { // The working sample count is the highest sample number for // which all used signals have data available, so go through all // channels and use the lowest overall sample count of the segment int64_t count = std::numeric_limits::max(); bool no_signals_assigned = true; for (const data::DecodeChannel& ch : channels_) if (ch.assigned_signal) { no_signals_assigned = false; const shared_ptr logic_data = ch.assigned_signal->logic_data(); if (!logic_data || logic_data->logic_segments().empty()) return 0; try { const shared_ptr segment = logic_data->logic_segments().at(segment_id); count = min(count, (int64_t)segment->get_sample_count()); } catch (out_of_range&) { return 0; } } return (no_signals_assigned ? 0 : count); } int64_t DecodeSignal::get_decoded_sample_count(uint32_t segment_id, bool include_processing) const { lock_guard decode_lock(output_mutex_); int64_t result = 0; try { const DecodeSegment *segment = &(segments_.at(segment_id)); if (include_processing) result = segment->samples_decoded_incl; else result = segment->samples_decoded_excl; } catch (out_of_range&) { // Do nothing } return result; } vector DecodeSignal::visible_rows() const { lock_guard lock(output_mutex_); vector rows; for (const shared_ptr& dec : stack_) { assert(dec); if (!dec->shown()) continue; const srd_decoder *const decc = dec->decoder(); assert(dec->decoder()); int row_index = 0; // Add a row for the decoder if it doesn't have a row list if (!decc->annotation_rows) rows.emplace_back(row_index++, decc); // Add the decoder rows for (const GSList *l = decc->annotation_rows; l; l = l->next) { const srd_decoder_annotation_row *const ann_row = (srd_decoder_annotation_row *)l->data; assert(ann_row); rows.emplace_back(row_index++, decc, ann_row); } } return rows; } void DecodeSignal::get_annotation_subset( vector &dest, const decode::Row &row, uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const { lock_guard lock(output_mutex_); try { const DecodeSegment *segment = &(segments_.at(segment_id)); const map *rows = &(segment->annotation_rows); const auto iter = rows->find(row); if (iter != rows->end()) (*iter).second.get_annotation_subset(dest, start_sample, end_sample); } catch (out_of_range&) { // Do nothing } } void DecodeSignal::get_annotation_subset( vector &dest, uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const { // Note: We put all vectors and lists on the heap, not the stack const vector rows = visible_rows(); // Use forward_lists for faster merging forward_list *all_ann_list = new forward_list(); for (const Row& row : rows) { vector *ann_vector = new vector(); get_annotation_subset(*ann_vector, row, segment_id, start_sample, end_sample); forward_list *ann_list = new forward_list(ann_vector->begin(), ann_vector->end()); delete ann_vector; all_ann_list->merge(*ann_list); delete ann_list; } move(all_ann_list->begin(), all_ann_list->end(), back_inserter(dest)); delete all_ann_list; } void DecodeSignal::save_settings(QSettings &settings) const { SignalBase::save_settings(settings); settings.setValue("decoders", (int)(stack_.size())); // Save decoder stack int decoder_idx = 0; for (const shared_ptr& decoder : stack_) { settings.beginGroup("decoder" + QString::number(decoder_idx++)); settings.setValue("id", decoder->decoder()->id); // Save decoder options const map& options = decoder->options(); settings.setValue("options", (int)options.size()); // Note: decode::Decoder::options() returns only the options // that differ from the default. See binding::Decoder::getter() int i = 0; for (auto& option : options) { settings.beginGroup("option" + QString::number(i)); settings.setValue("name", QString::fromStdString(option.first)); GlobalSettings::store_gvariant(settings, option.second); settings.endGroup(); i++; } settings.endGroup(); } // Save channel mapping settings.setValue("channels", (int)channels_.size()); for (unsigned int channel_id = 0; channel_id < channels_.size(); channel_id++) { auto channel = find_if(channels_.begin(), channels_.end(), [&](data::DecodeChannel ch) { return ch.id == channel_id; }); if (channel == channels_.end()) { qDebug() << "ERROR: Gap in channel index:" << channel_id; continue; } settings.beginGroup("channel" + QString::number(channel_id)); settings.setValue("name", channel->name); // Useful for debugging settings.setValue("initial_pin_state", channel->initial_pin_state); if (channel->assigned_signal) settings.setValue("assigned_signal_name", channel->assigned_signal->name()); settings.endGroup(); } } void DecodeSignal::restore_settings(QSettings &settings) { SignalBase::restore_settings(settings); // Restore decoder stack GSList *dec_list = g_slist_copy((GSList*)srd_decoder_list()); int decoders = settings.value("decoders").toInt(); for (int decoder_idx = 0; decoder_idx < decoders; decoder_idx++) { settings.beginGroup("decoder" + QString::number(decoder_idx)); QString id = settings.value("id").toString(); for (GSList *entry = dec_list; entry; entry = entry->next) { const srd_decoder *dec = (srd_decoder*)entry->data; if (!dec) continue; if (QString::fromUtf8(dec->id) == id) { shared_ptr decoder = make_shared(dec); stack_.push_back(decoder); // Restore decoder options that differ from their default int options = settings.value("options").toInt(); for (int i = 0; i < options; i++) { settings.beginGroup("option" + QString::number(i)); QString name = settings.value("name").toString(); GVariant *value = GlobalSettings::restore_gvariant(settings); decoder->set_option(name.toUtf8(), value); settings.endGroup(); } // Include the newly created decode channels in the channel lists update_channel_list(); break; } } settings.endGroup(); channels_updated(); } // Restore channel mapping unsigned int channels = settings.value("channels").toInt(); const unordered_set< shared_ptr > signalbases = session_.signalbases(); for (unsigned int channel_id = 0; channel_id < channels; channel_id++) { auto channel = find_if(channels_.begin(), channels_.end(), [&](data::DecodeChannel ch) { return ch.id == channel_id; }); if (channel == channels_.end()) { qDebug() << "ERROR: Non-existant channel index:" << channel_id; continue; } settings.beginGroup("channel" + QString::number(channel_id)); QString assigned_signal_name = settings.value("assigned_signal_name").toString(); for (const shared_ptr& signal : signalbases) if (signal->name() == assigned_signal_name) channel->assigned_signal = signal.get(); channel->initial_pin_state = settings.value("initial_pin_state").toInt(); settings.endGroup(); } // Update the internal structures stack_config_changed_ = true; update_channel_list(); commit_decoder_channels(); begin_decode(); } void DecodeSignal::set_error_message(QString msg) { error_message_ = msg; // TODO Emulate noquote() qDebug().nospace() << name() << ": " << msg; } uint32_t DecodeSignal::get_input_segment_count() const { uint64_t count = std::numeric_limits::max(); bool no_signals_assigned = true; for (const data::DecodeChannel& ch : channels_) if (ch.assigned_signal) { no_signals_assigned = false; const shared_ptr logic_data = ch.assigned_signal->logic_data(); if (!logic_data || logic_data->logic_segments().empty()) return 0; // Find the min value of all segment counts if ((uint64_t)(logic_data->logic_segments().size()) < count) count = logic_data->logic_segments().size(); } return (no_signals_assigned ? 0 : count); } uint32_t DecodeSignal::get_input_samplerate(uint32_t segment_id) const { double samplerate = 0; for (const data::DecodeChannel& ch : channels_) if (ch.assigned_signal) { const shared_ptr logic_data = ch.assigned_signal->logic_data(); if (!logic_data || logic_data->logic_segments().empty()) continue; try { const shared_ptr segment = logic_data->logic_segments().at(segment_id); samplerate = segment->samplerate(); } catch (out_of_range&) { // Do nothing } break; } return samplerate; } void DecodeSignal::update_channel_list() { vector prev_channels = channels_; channels_.clear(); uint16_t id = 0; // Copy existing entries, create new as needed for (shared_ptr& decoder : stack_) { const srd_decoder* srd_d = decoder->decoder(); const GSList *l; // Mandatory channels for (l = srd_d->channels; l; l = l->next) { const struct srd_channel *const pdch = (struct srd_channel *)l->data; bool ch_added = false; // Copy but update ID if this channel was in the list before for (data::DecodeChannel& ch : prev_channels) if (ch.pdch_ == pdch) { ch.id = id++; channels_.push_back(ch); ch_added = true; break; } if (!ch_added) { // Create new entry without a mapped signal data::DecodeChannel ch = {id++, 0, false, nullptr, QString::fromUtf8(pdch->name), QString::fromUtf8(pdch->desc), SRD_INITIAL_PIN_SAME_AS_SAMPLE0, decoder, pdch}; channels_.push_back(ch); } } // Optional channels for (l = srd_d->opt_channels; l; l = l->next) { const struct srd_channel *const pdch = (struct srd_channel *)l->data; bool ch_added = false; // Copy but update ID if this channel was in the list before for (data::DecodeChannel& ch : prev_channels) if (ch.pdch_ == pdch) { ch.id = id++; channels_.push_back(ch); ch_added = true; break; } if (!ch_added) { // Create new entry without a mapped signal data::DecodeChannel ch = {id++, 0, true, nullptr, QString::fromUtf8(pdch->name), QString::fromUtf8(pdch->desc), SRD_INITIAL_PIN_SAME_AS_SAMPLE0, decoder, pdch}; channels_.push_back(ch); } } } // Invalidate the logic output data if the channel assignment changed if (prev_channels.size() != channels_.size()) { // The number of channels changed, there's definitely a difference logic_mux_data_invalid_ = true; } else { // Same number but assignment may still differ, so compare all channels for (size_t i = 0; i < channels_.size(); i++) { const data::DecodeChannel& p_ch = prev_channels[i]; const data::DecodeChannel& ch = channels_[i]; if ((p_ch.pdch_ != ch.pdch_) || (p_ch.assigned_signal != ch.assigned_signal)) { logic_mux_data_invalid_ = true; break; } } } channels_updated(); } void DecodeSignal::commit_decoder_channels() { // Submit channel list to every decoder, containing only the relevant channels for (shared_ptr dec : stack_) { vector channel_list; for (data::DecodeChannel& ch : channels_) if (ch.decoder_ == dec) channel_list.push_back(&ch); dec->set_channels(channel_list); } // Channel bit IDs must be in sync with the channel's apperance in channels_ int id = 0; for (data::DecodeChannel& ch : channels_) if (ch.assigned_signal) ch.bit_id = id++; } void DecodeSignal::mux_logic_samples(uint32_t segment_id, const int64_t start, const int64_t end) { // Enforce end to be greater than start if (end <= start) return; // Fetch the channel segments and their data vector > segments; vector signal_data; vector signal_in_bytepos; vector signal_in_bitpos; for (data::DecodeChannel& ch : channels_) if (ch.assigned_signal) { const shared_ptr logic_data = ch.assigned_signal->logic_data(); shared_ptr segment; try { segment = logic_data->logic_segments().at(segment_id); } catch (out_of_range&) { qDebug() << "Muxer error for" << name() << ":" << ch.assigned_signal->name() \ << "has no logic segment" << segment_id; return; } segments.push_back(segment); uint8_t* data = new uint8_t[(end - start) * segment->unit_size()]; segment->get_samples(start, end, data); signal_data.push_back(data); const int bitpos = ch.assigned_signal->logic_bit_index(); signal_in_bytepos.push_back(bitpos / 8); signal_in_bitpos.push_back(bitpos % 8); } shared_ptr output_segment; try { output_segment = logic_mux_data_->logic_segments().at(segment_id); } catch (out_of_range&) { qDebug() << "Muxer error for" << name() << ": no logic mux segment" \ << segment_id << "in mux_logic_samples(), mux segments size is" \ << logic_mux_data_->logic_segments().size(); return; } // Perform the muxing of signal data into the output data uint8_t* output = new uint8_t[(end - start) * output_segment->unit_size()]; unsigned int signal_count = signal_data.size(); for (int64_t sample_cnt = 0; !logic_mux_interrupt_ && (sample_cnt < (end - start)); sample_cnt++) { int bitpos = 0; uint8_t bytepos = 0; const int out_sample_pos = sample_cnt * output_segment->unit_size(); for (unsigned int i = 0; i < output_segment->unit_size(); i++) output[out_sample_pos + i] = 0; for (unsigned int i = 0; i < signal_count; i++) { const int in_sample_pos = sample_cnt * segments[i]->unit_size(); const uint8_t in_sample = 1 & ((signal_data[i][in_sample_pos + signal_in_bytepos[i]]) >> (signal_in_bitpos[i])); const uint8_t out_sample = output[out_sample_pos + bytepos]; output[out_sample_pos + bytepos] = out_sample | (in_sample << bitpos); bitpos++; if (bitpos > 7) { bitpos = 0; bytepos++; } } } output_segment->append_payload(output, (end - start) * output_segment->unit_size()); delete[] output; for (const uint8_t* data : signal_data) delete[] data; } void DecodeSignal::logic_mux_proc() { uint32_t segment_id = 0; assert(logic_mux_data_); // Create initial logic mux segment shared_ptr output_segment = make_shared(*logic_mux_data_, segment_id, logic_mux_unit_size_, 0); logic_mux_data_->push_segment(output_segment); output_segment->set_samplerate(get_input_samplerate(0)); do { const uint64_t input_sample_count = get_working_sample_count(segment_id); const uint64_t output_sample_count = output_segment->get_sample_count(); const uint64_t samples_to_process = (input_sample_count > output_sample_count) ? (input_sample_count - output_sample_count) : 0; // Process the samples if necessary... if (samples_to_process > 0) { const uint64_t unit_size = output_segment->unit_size(); const uint64_t chunk_sample_count = DecodeChunkLength / unit_size; uint64_t processed_samples = 0; do { const uint64_t start_sample = output_sample_count + processed_samples; const uint64_t sample_count = min(samples_to_process - processed_samples, chunk_sample_count); mux_logic_samples(segment_id, start_sample, start_sample + sample_count); processed_samples += sample_count; // ...and process the newly muxed logic data decode_input_cond_.notify_one(); } while (!logic_mux_interrupt_ && (processed_samples < samples_to_process)); } if (samples_to_process == 0) { // TODO Optimize this by caching the input segment count and only // querying it when the cached value was reached if (segment_id < get_input_segment_count() - 1) { // Process next segment segment_id++; output_segment = make_shared(*logic_mux_data_, segment_id, logic_mux_unit_size_, 0); logic_mux_data_->push_segment(output_segment); output_segment->set_samplerate(get_input_samplerate(segment_id)); } else { // All segments have been processed logic_mux_data_invalid_ = false; // Wait for more input unique_lock logic_mux_lock(logic_mux_mutex_); logic_mux_cond_.wait(logic_mux_lock); } } } while (!logic_mux_interrupt_); } void DecodeSignal::decode_data( const int64_t abs_start_samplenum, const int64_t sample_count, const shared_ptr input_segment) { const int64_t unit_size = input_segment->unit_size(); const int64_t chunk_sample_count = DecodeChunkLength / unit_size; for (int64_t i = abs_start_samplenum; error_message_.isEmpty() && !decode_interrupt_ && (i < (abs_start_samplenum + sample_count)); i += chunk_sample_count) { const int64_t chunk_end = min(i + chunk_sample_count, abs_start_samplenum + sample_count); { lock_guard lock(output_mutex_); // Update the sample count showing the samples including currently processed ones segments_.at(current_segment_id_).samples_decoded_incl = chunk_end; } int64_t data_size = (chunk_end - i) * unit_size; uint8_t* chunk = new uint8_t[data_size]; input_segment->get_samples(i, chunk_end, chunk); if (srd_session_send(srd_session_, i, chunk_end, chunk, data_size, unit_size) != SRD_OK) set_error_message(tr("Decoder reported an error")); delete[] chunk; { lock_guard lock(output_mutex_); // Now that all samples are processed, the exclusive sample count catches up segments_.at(current_segment_id_).samples_decoded_excl = chunk_end; } // Notify the frontend that we processed some data and // possibly have new annotations as well new_annotations(); if (decode_paused_) { unique_lock pause_wait_lock(decode_pause_mutex_); decode_pause_cond_.wait(pause_wait_lock); } } } void DecodeSignal::decode_proc() { current_segment_id_ = 0; // If there is no input data available yet, wait until it is or we're interrupted if (logic_mux_data_->logic_segments().size() == 0) { unique_lock input_wait_lock(input_mutex_); decode_input_cond_.wait(input_wait_lock); } if (decode_interrupt_) return; shared_ptr input_segment = logic_mux_data_->logic_segments().front(); assert(input_segment); // Create the initial segment and set its sample rate so that we can pass it to SRD create_decode_segment(); segments_.at(current_segment_id_).samplerate = input_segment->samplerate(); segments_.at(current_segment_id_).start_time = input_segment->start_time(); start_srd_session(); uint64_t sample_count = 0; uint64_t abs_start_samplenum = 0; do { // Keep processing new samples until we exhaust the input data do { lock_guard input_lock(input_mutex_); sample_count = input_segment->get_sample_count() - abs_start_samplenum; if (sample_count > 0) { decode_data(abs_start_samplenum, sample_count, input_segment); abs_start_samplenum += sample_count; } } while (error_message_.isEmpty() && (sample_count > 0) && !decode_interrupt_); if (error_message_.isEmpty() && !decode_interrupt_ && sample_count == 0) { if (current_segment_id_ < logic_mux_data_->logic_segments().size() - 1) { // Process next segment current_segment_id_++; try { input_segment = logic_mux_data_->logic_segments().at(current_segment_id_); } catch (out_of_range&) { qDebug() << "Decode error for" << name() << ": no logic mux segment" \ << current_segment_id_ << "in decode_proc(), mux segments size is" \ << logic_mux_data_->logic_segments().size(); return; } abs_start_samplenum = 0; // Create the next segment and set its metadata create_decode_segment(); segments_.at(current_segment_id_).samplerate = input_segment->samplerate(); segments_.at(current_segment_id_).start_time = input_segment->start_time(); // Reset decoder state but keep the decoder stack intact terminate_srd_session(); } else { // All segments have been processed decode_finished(); // Wait for new input data or an interrupt was requested unique_lock input_wait_lock(input_mutex_); decode_input_cond_.wait(input_wait_lock); } } } while (error_message_.isEmpty() && !decode_interrupt_); // Potentially reap decoders when the application no longer is // interested in their (pending) results. if (decode_interrupt_) terminate_srd_session(); } void DecodeSignal::start_srd_session() { // If there were stack changes, the session has been destroyed by now, so if // it hasn't been destroyed, we can just reset and re-use it if (srd_session_) { // When a decoder stack was created before, re-use it // for the next stream of input data, after terminating // potentially still executing operations, and resetting // internal state. Skip the rather expensive (teardown // and) construction of another decoder stack. // TODO Reduce redundancy, use a common code path for // the meta/start sequence? terminate_srd_session(); // Metadata is cleared also, so re-set it uint64_t samplerate = 0; if (segments_.size() > 0) samplerate = segments_.at(current_segment_id_).samplerate; if (samplerate) srd_session_metadata_set(srd_session_, SRD_CONF_SAMPLERATE, g_variant_new_uint64(samplerate)); for (const shared_ptr& dec : stack_) dec->apply_all_options(); srd_session_start(srd_session_); return; } // Create the session srd_session_new(&srd_session_); assert(srd_session_); // Create the decoders srd_decoder_inst *prev_di = nullptr; for (const shared_ptr& dec : stack_) { srd_decoder_inst *const di = dec->create_decoder_inst(srd_session_); if (!di) { set_error_message(tr("Failed to create decoder instance")); srd_session_destroy(srd_session_); srd_session_ = nullptr; return; } if (prev_di) srd_inst_stack(srd_session_, prev_di, di); prev_di = di; } // Start the session if (segments_.size() > 0) srd_session_metadata_set(srd_session_, SRD_CONF_SAMPLERATE, g_variant_new_uint64(segments_.at(current_segment_id_).samplerate)); srd_pd_output_callback_add(srd_session_, SRD_OUTPUT_ANN, DecodeSignal::annotation_callback, this); srd_session_start(srd_session_); // We just recreated the srd session, so all stack changes are applied now stack_config_changed_ = false; } void DecodeSignal::terminate_srd_session() { // Call the "terminate and reset" routine for the decoder stack // (if available). This does not harm those stacks which already // have completed their operation, and reduces response time for // those stacks which still are processing data while the // application no longer wants them to. if (srd_session_) { srd_session_terminate_reset(srd_session_); // Metadata is cleared also, so re-set it uint64_t samplerate = 0; if (segments_.size() > 0) samplerate = segments_.at(current_segment_id_).samplerate; if (samplerate) srd_session_metadata_set(srd_session_, SRD_CONF_SAMPLERATE, g_variant_new_uint64(samplerate)); for (const shared_ptr& dec : stack_) dec->apply_all_options(); } } void DecodeSignal::stop_srd_session() { if (srd_session_) { // Destroy the session srd_session_destroy(srd_session_); srd_session_ = nullptr; // Mark the decoder instances as non-existant since they were deleted for (const shared_ptr& dec : stack_) dec->invalidate_decoder_inst(); } } void DecodeSignal::connect_input_notifiers() { // Disconnect the notification slot from the previous set of signals disconnect(this, SLOT(on_data_cleared())); disconnect(this, SLOT(on_data_received())); // Connect the currently used signals to our slot for (data::DecodeChannel& ch : channels_) { if (!ch.assigned_signal) continue; const data::SignalBase *signal = ch.assigned_signal; connect(signal, SIGNAL(samples_cleared()), this, SLOT(on_data_cleared())); connect(signal, SIGNAL(samples_added(uint64_t, uint64_t, uint64_t)), this, SLOT(on_data_received())); } } void DecodeSignal::create_decode_segment() { // Create annotation segment segments_.emplace_back(DecodeSegment()); // Add annotation classes for (const shared_ptr& dec : stack_) { assert(dec); const srd_decoder *const decc = dec->decoder(); assert(dec->decoder()); int row_index = 0; // Add a row for the decoder if it doesn't have a row list if (!decc->annotation_rows) (segments_.back().annotation_rows)[Row(row_index++, decc)] = decode::RowData(); // Add the decoder rows for (const GSList *l = decc->annotation_rows; l; l = l->next) { const srd_decoder_annotation_row *const ann_row = (srd_decoder_annotation_row *)l->data; assert(ann_row); const Row row(row_index++, decc, ann_row); // Add a new empty row data object (segments_.back().annotation_rows)[row] = decode::RowData(); } } } void DecodeSignal::annotation_callback(srd_proto_data *pdata, void *decode_signal) { assert(pdata); assert(decode_signal); DecodeSignal *const ds = (DecodeSignal*)decode_signal; assert(ds); if (ds->decode_interrupt_) return; lock_guard lock(ds->output_mutex_); // Find the row assert(pdata->pdo); assert(pdata->pdo->di); const srd_decoder *const decc = pdata->pdo->di->decoder; assert(decc); const srd_proto_data_annotation *const pda = (const srd_proto_data_annotation*)pdata->data; assert(pda); auto row_iter = ds->segments_.at(ds->current_segment_id_).annotation_rows.end(); // Try looking up the sub-row of this class const auto format = pda->ann_class; const auto r = ds->class_rows_.find(make_pair(decc, format)); if (r != ds->class_rows_.end()) row_iter = ds->segments_.at(ds->current_segment_id_).annotation_rows.find((*r).second); else { // Failing that, use the decoder as a key row_iter = ds->segments_.at(ds->current_segment_id_).annotation_rows.find(Row(0, decc)); } if (row_iter == ds->segments_.at(ds->current_segment_id_).annotation_rows.end()) { qDebug() << "Unexpected annotation: decoder = " << decc << ", format = " << format; assert(false); return; } // Add the annotation (*row_iter).second.emplace_annotation(pdata, &((*row_iter).first)); } void DecodeSignal::on_capture_state_changed(int state) { // If a new acquisition was started, we need to start decoding from scratch if (state == Session::Running) { logic_mux_data_invalid_ = true; begin_decode(); } } void DecodeSignal::on_data_cleared() { reset_decode(); } void DecodeSignal::on_data_received() { // If we detected a lack of input data when trying to start decoding, // we have set an error message. Only try again if we now have data // to work with if ((!error_message_.isEmpty()) && (get_input_segment_count() == 0)) return; if (!logic_mux_thread_.joinable()) begin_decode(); else logic_mux_cond_.notify_one(); } } // namespace data } // namespace pv pulseview-0.4.1/pv/data/decode/000700 001750 001750 00000000000 13365562011 015752 5ustar00uweuwe000000 000000 pulseview-0.4.1/pv/data/decode/row.hpp000600 001750 001750 00000003020 13365561737 017305 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DATA_DECODE_ROW_HPP #define PULSEVIEW_PV_DATA_DECODE_ROW_HPP #include #include "annotation.hpp" struct srd_decoder; struct srd_decoder_annotation_row; namespace pv { namespace data { namespace decode { class Row { public: Row(); Row(int index, const srd_decoder *decoder, const srd_decoder_annotation_row *row = nullptr); const srd_decoder* decoder() const; const srd_decoder_annotation_row* row() const; const QString title() const; const QString class_name() const; int index() const; bool operator<(const Row &other) const; private: int index_; const srd_decoder *decoder_; const srd_decoder_annotation_row *row_; }; } // namespace decode } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_DECODE_ROW_HPP pulseview-0.4.1/pv/data/decode/annotation.hpp000600 001750 001750 00000003110 13365561737 020650 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_HPP #define PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_HPP #include #include #include using std::vector; struct srd_proto_data; namespace pv { namespace data { namespace decode { class Row; class Annotation { public: typedef uint32_t Class; public: Annotation(const srd_proto_data *const pdata, const Row *row); uint64_t start_sample() const; uint64_t end_sample() const; Class ann_class() const; const vector& annotations() const; const Row* row() const; bool operator<(const Annotation &other) const; private: uint64_t start_sample_; uint64_t end_sample_; Class ann_class_; vector annotations_; const Row *row_; }; } // namespace decode } // namespace data } // namespace pv #endif // PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_HPP pulseview-0.4.1/pv/data/decode/decoder.hpp000600 001750 001750 00000003763 13365561737 020121 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DATA_DECODE_DECODER_HPP #define PULSEVIEW_PV_DATA_DECODE_DECODER_HPP #include #include #include #include #include using std::map; using std::string; using std::vector; struct srd_decoder; struct srd_decoder_inst; struct srd_channel; struct srd_session; namespace pv { namespace data { struct DecodeChannel; class Logic; class SignalBase; namespace decode { class Decoder { public: Decoder(const srd_decoder *const dec); virtual ~Decoder(); const srd_decoder* decoder() const; bool shown() const; void show(bool show = true); const vector& channels() const; void set_channels(vector channels); const map& options() const; void set_option(const char *id, GVariant *value); void apply_all_options(); bool have_required_channels() const; srd_decoder_inst* create_decoder_inst(srd_session *session); void invalidate_decoder_inst(); private: const srd_decoder *const decoder_; bool shown_; vector channels_; map options_; srd_decoder_inst *decoder_inst_; }; } // namespace decode } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_DECODE_DECODER_HPP pulseview-0.4.1/pv/data/decode/decoder.cpp000600 001750 001750 00000010156 13365561737 020106 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include "decoder.hpp" #include #include using pv::data::DecodeChannel; using std::map; using std::string; namespace pv { namespace data { namespace decode { Decoder::Decoder(const srd_decoder *const dec) : decoder_(dec), shown_(true), decoder_inst_(nullptr) { } Decoder::~Decoder() { for (auto& option : options_) g_variant_unref(option.second); } const srd_decoder* Decoder::decoder() const { return decoder_; } bool Decoder::shown() const { return shown_; } void Decoder::show(bool show) { shown_ = show; } const vector& Decoder::channels() const { return channels_; } void Decoder::set_channels(vector channels) { channels_ = channels; } const map& Decoder::options() const { return options_; } void Decoder::set_option(const char *id, GVariant *value) { assert(value); g_variant_ref(value); options_[id] = value; // If we have a decoder instance, apply option value immediately apply_all_options(); } void Decoder::apply_all_options() { if (decoder_inst_) { GHashTable *const opt_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref); for (const auto& option : options_) { GVariant *const value = option.second; g_variant_ref(value); g_hash_table_replace(opt_hash, (void*)g_strdup( option.first.c_str()), value); } srd_inst_option_set(decoder_inst_, opt_hash); g_hash_table_destroy(opt_hash); } } bool Decoder::have_required_channels() const { for (DecodeChannel *ch : channels_) if (!ch->assigned_signal && !ch->is_optional) return false; return true; } srd_decoder_inst* Decoder::create_decoder_inst(srd_session *session) { GHashTable *const opt_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref); for (const auto& option : options_) { GVariant *const value = option.second; g_variant_ref(value); g_hash_table_replace(opt_hash, (void*)g_strdup( option.first.c_str()), value); } if (decoder_inst_) qDebug() << "WARNING: previous decoder instance" << decoder_inst_ << "exists"; decoder_inst_ = srd_inst_new(session, decoder_->id, opt_hash); g_hash_table_destroy(opt_hash); if (!decoder_inst_) return nullptr; // Setup the channels GArray *const init_pin_states = g_array_sized_new(false, true, sizeof(uint8_t), channels_.size()); g_array_set_size(init_pin_states, channels_.size()); GHashTable *const channels = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref); for (DecodeChannel *ch : channels_) { if (!ch->assigned_signal) continue; init_pin_states->data[ch->id] = ch->initial_pin_state; GVariant *const gvar = g_variant_new_int32(ch->bit_id); // bit_id = bit position g_variant_ref_sink(gvar); // key is channel name (pdch->id), value is bit position in each sample (gvar) g_hash_table_insert(channels, ch->pdch_->id, gvar); } srd_inst_channel_set_all(decoder_inst_, channels); srd_inst_initial_pins_set_all(decoder_inst_, init_pin_states); g_array_free(init_pin_states, true); return decoder_inst_; } void Decoder::invalidate_decoder_inst() { decoder_inst_ = nullptr; } } // namespace decode } // namespace data } // namespace pv pulseview-0.4.1/pv/data/decode/annotation.cpp000600 001750 001750 00000003664 13365561737 020661 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ extern "C" { #include } #include #include #include "annotation.hpp" using std::vector; namespace pv { namespace data { namespace decode { Annotation::Annotation(const srd_proto_data *const pdata, const Row *row) : start_sample_(pdata->start_sample), end_sample_(pdata->end_sample), row_(row) { assert(pdata); const srd_proto_data_annotation *const pda = (const srd_proto_data_annotation*)pdata->data; assert(pda); ann_class_ = (Class)(pda->ann_class); const char *const *annotations = (char**)pda->ann_text; while (*annotations) { annotations_.push_back(QString::fromUtf8(*annotations)); annotations++; } } uint64_t Annotation::start_sample() const { return start_sample_; } uint64_t Annotation::end_sample() const { return end_sample_; } Annotation::Class Annotation::ann_class() const { return ann_class_; } const vector& Annotation::annotations() const { return annotations_; } const Row* Annotation::row() const { return row_; } bool Annotation::operator<(const Annotation &other) const { return (start_sample_ < other.start_sample_); } } // namespace decode } // namespace data } // namespace pv pulseview-0.4.1/pv/data/decode/rowdata.cpp000600 001750 001750 00000002710 13365561737 020137 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "rowdata.hpp" using std::vector; namespace pv { namespace data { namespace decode { uint64_t RowData::get_max_sample() const { if (annotations_.empty()) return 0; return annotations_.back().end_sample(); } void RowData::get_annotation_subset( vector &dest, uint64_t start_sample, uint64_t end_sample) const { for (const auto& annotation : annotations_) if (annotation.end_sample() > start_sample && annotation.start_sample() <= end_sample) dest.push_back(annotation); } void RowData::emplace_annotation(srd_proto_data *pdata, const Row *row) { annotations_.emplace_back(pdata, row); } } // namespace decode } // namespace data } // namespace pv pulseview-0.4.1/pv/data/decode/rowdata.hpp000600 001750 001750 00000003217 13365561737 020147 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DATA_DECODE_ROWDATA_HPP #define PULSEVIEW_PV_DATA_DECODE_ROWDATA_HPP #include #include #include "annotation.hpp" using std::vector; namespace pv { namespace data { namespace decode { class Row; class RowData { public: RowData() = default; public: uint64_t get_max_sample() const; /** * Extracts annotations between the given sample range into a vector. * Note: The annotations are unsorted and only annotations that fully * fit into the sample range are considered. */ void get_annotation_subset( vector &dest, uint64_t start_sample, uint64_t end_sample) const; void emplace_annotation(srd_proto_data *pdata, const Row *row); private: vector annotations_; }; } // namespace decode } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_DECODE_ROWDATA_HPP pulseview-0.4.1/pv/data/decode/row.cpp000600 001750 001750 00000003643 13365561737 017313 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "row.hpp" #include namespace pv { namespace data { namespace decode { Row::Row() : decoder_(nullptr), row_(nullptr) { } Row::Row(int index, const srd_decoder *decoder, const srd_decoder_annotation_row *row) : index_(index), decoder_(decoder), row_(row) { } const srd_decoder* Row::decoder() const { return decoder_; } const srd_decoder_annotation_row* Row::row() const { return row_; } const QString Row::title() const { if (decoder_ && decoder_->name && row_ && row_->desc) return QString("%1: %2") .arg(QString::fromUtf8(decoder_->name), QString::fromUtf8(row_->desc)); if (decoder_ && decoder_->name) return QString::fromUtf8(decoder_->name); if (row_ && row_->desc) return QString::fromUtf8(row_->desc); return QString(); } const QString Row::class_name() const { if (row_ && row_->desc) return QString::fromUtf8(row_->desc); return QString(); } int Row::index() const { return index_; } bool Row::operator<(const Row &other) const { return (decoder_ < other.decoder_) || (decoder_ == other.decoder_ && row_ < other.row_); } } // namespace decode } // namespace data } // namespace pv pulseview-0.4.1/pv/data/segment.hpp000600 001750 001750 00000006516 13365561737 016732 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2017 Soeren Apel * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DATA_SEGMENT_HPP #define PULSEVIEW_PV_DATA_SEGMENT_HPP #include "pv/util.hpp" #include #include #include #include using std::recursive_mutex; using std::vector; namespace SegmentTest { struct SmallSize8Single; struct MediumSize8Single; struct MaxSize8Single; struct MediumSize24Single; struct MediumSize32Single; struct MaxSize32Single; struct MediumSize32Multi; struct MaxSize32Multi; struct MaxSize32MultiAtOnce; struct MaxSize32MultiIterated; } // namespace SegmentTest namespace pv { namespace data { typedef struct { uint64_t sample_index, chunk_num, chunk_offs; uint8_t* chunk; } SegmentDataIterator; class Segment : public QObject { Q_OBJECT private: static const uint64_t MaxChunkSize; public: Segment(uint32_t segment_id, uint64_t samplerate, unsigned int unit_size); virtual ~Segment(); uint64_t get_sample_count() const; const pv::util::Timestamp& start_time() const; double samplerate() const; void set_samplerate(double samplerate); unsigned int unit_size() const; uint32_t segment_id() const; void set_complete(); bool is_complete() const; void free_unused_memory(); protected: void append_single_sample(void *data); void append_samples(void *data, uint64_t samples); void get_raw_samples(uint64_t start, uint64_t count, uint8_t *dest) const; SegmentDataIterator* begin_sample_iteration(uint64_t start); void continue_sample_iteration(SegmentDataIterator* it, uint64_t increase); void end_sample_iteration(SegmentDataIterator* it); uint8_t* get_iterator_value(SegmentDataIterator* it); uint64_t get_iterator_valid_length(SegmentDataIterator* it); uint32_t segment_id_; mutable recursive_mutex mutex_; vector data_chunks_; uint8_t* current_chunk_; uint64_t used_samples_, unused_samples_; uint64_t sample_count_; pv::util::Timestamp start_time_; double samplerate_; uint64_t chunk_size_; unsigned int unit_size_; int iterator_count_; bool mem_optimization_requested_; bool is_complete_; friend struct SegmentTest::SmallSize8Single; friend struct SegmentTest::MediumSize8Single; friend struct SegmentTest::MaxSize8Single; friend struct SegmentTest::MediumSize24Single; friend struct SegmentTest::MediumSize32Single; friend struct SegmentTest::MaxSize32Single; friend struct SegmentTest::MediumSize32Multi; friend struct SegmentTest::MaxSize32Multi; friend struct SegmentTest::MaxSize32MultiAtOnce; friend struct SegmentTest::MaxSize32MultiIterated; }; } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_SEGMENT_HPP pulseview-0.4.1/pv/data/signalbase.hpp000600 001750 001750 00000021713 13365561737 017374 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * Copyright (C) 2016 Soeren Apel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DATA_SIGNALBASE_HPP #define PULSEVIEW_PV_DATA_SIGNALBASE_HPP #include #include #include #include #include #include #include #include #include #include #include using std::atomic; using std::condition_variable; using std::map; using std::mutex; using std::pair; using std::shared_ptr; using std::vector; namespace sigrok { class Channel; } namespace pv { namespace data { class Analog; class AnalogSegment; class DecoderStack; class Logic; class LogicSegment; class SignalData; class SignalBase : public QObject { Q_OBJECT public: enum ChannelType { AnalogChannel = 1, ///< Analog data LogicChannel, ///< Logic data DecodeChannel, ///< Protocol Decoder channel using libsigrokdecode MathChannel ///< Virtual channel generated by math operations }; enum ConversionType { NoConversion = 0, A2LConversionByThreshold = 1, A2LConversionBySchmittTrigger = 2 }; /** * Conversion presets range from -1 to n, where 1..n are dependent on * the conversion these presets apply to. -1 and 0 have fixed meanings, * however. */ enum ConversionPreset { NoPreset = -1, ///< Conversion uses custom values DynamicPreset = 0 ///< Conversion uses calculated values }; private: static const int ColorBGAlpha; static const uint64_t ConversionBlockSize; static const uint32_t ConversionDelay; public: SignalBase(shared_ptr channel, ChannelType channel_type); virtual ~SignalBase(); public: /** * Returns the underlying SR channel. */ shared_ptr channel() const; /** * Returns enabled status of this channel. */ bool enabled() const; /** * Sets the enabled status of this channel. * @param value Boolean value to set. */ void set_enabled(bool value); /** * Gets the type of this channel. */ ChannelType type() const; /** * Gets the index number of this channel, i.e. a unique ID assigned by * the device driver. */ unsigned int index() const; /** * Returns which bit of a given sample for this signal represents the * signal itself. This is relevant for compound signals like logic, * rather meaningless for everything else but provided in case there * is a conversion active that provides a digital signal using bit #0. */ unsigned int logic_bit_index() const; /** * Gets the name of this signal. */ QString name() const; /** * Gets the internal name of this signal, i.e. how the device calls it. */ QString internal_name() const; /** * Produces a string for this signal that can be used for display, * i.e. it contains one or both of the signal/internal names. */ QString display_name() const; /** * Sets the name of the signal. */ virtual void set_name(QString name); /** * Get the color of the signal. */ QColor color() const; /** * Set the color of the signal. */ void set_color(QColor color); /** * Get the background color of the signal. */ QColor bgcolor() const; /** * Sets the internal data object. */ void set_data(shared_ptr data); /** * Get the internal data as analog data object in case of analog type. */ shared_ptr analog_data() const; /** * Get the internal data as logic data object in case of logic type. */ shared_ptr logic_data() const; /** * Determines whether a given segment is complete (i.e. end-of-frame has * been seen). It only considers the original data, not the converted data. */ bool segment_is_complete(uint32_t segment_id) const; /** * Determines whether this signal has any sample data at all. */ bool has_samples() const; /** * Returns the sample rate for this signal. */ double get_samplerate() const; /** * Queries the kind of conversion performed on this channel. */ ConversionType get_conversion_type() const; /** * Changes the kind of conversion performed on this channel. * * Restarts the conversion. */ void set_conversion_type(ConversionType t); /** * Returns all currently known conversion options */ map get_conversion_options() const; /** * Sets the value of a particular conversion option * Note: it is not checked whether the option is valid for the * currently conversion. If it's not, it will be silently ignored. * * Does not restart the conversion. * * @return true if the value is different from before, false otherwise */ bool set_conversion_option(QString key, QVariant value); /** * Returns the threshold(s) used for conversions, if applicable. * The resulting thresholds are given for the chosen conversion, so you * can query thresholds also for conversions which aren't currently active. * * If you want the thresholds for the currently active conversion, * call it either with NoConversion or no parameter. * * @param t the type of conversion to obtain the thresholds for, leave * empty or use NoConversion if you want to query the currently * used conversion * * @param always_custom ignore the currently selected preset and always * return the custom values for this conversion, using 0 if those * aren't set * * @return a list of threshold(s) used by the chosen conversion */ vector get_conversion_thresholds( const ConversionType t = NoConversion, const bool always_custom=false) const; /** * Provides all conversion presets available for the currently active * conversion. * * @return a list of description/ID pairs for each preset */ vector > get_conversion_presets() const; /** * Determines the ID of the currently used conversion preset, which is only * valid for the currently available conversion presets. It is therefore * suggested to call @ref get_conversion_presets right before calling this. * * @return the ID of the currently used conversion preset. -1 if no preset * is used. In that case, a user setting is used instead. */ ConversionPreset get_current_conversion_preset() const; /** * Sets the conversion preset to be used. * * Does not restart the conversion. * * @param id the id of the preset to use */ void set_conversion_preset(ConversionPreset id); #ifdef ENABLE_DECODE bool is_decode_signal() const; #endif virtual void save_settings(QSettings &settings) const; virtual void restore_settings(QSettings &settings); void start_conversion(bool delayed_start=false); private: bool conversion_is_a2l() const; uint8_t convert_a2l_threshold(float threshold, float value); uint8_t convert_a2l_schmitt_trigger(float lo_thr, float hi_thr, float value, uint8_t &state); void convert_single_segment_range(AnalogSegment *asegment, LogicSegment *lsegment, uint64_t start_sample, uint64_t end_sample); void convert_single_segment(pv::data::AnalogSegment *asegment, pv::data::LogicSegment *lsegment); void conversion_thread_proc(); void stop_conversion(); Q_SIGNALS: void enabled_changed(const bool &value); void name_changed(const QString &name); void color_changed(const QColor &color); void conversion_type_changed(const ConversionType t); void samples_cleared(); void samples_added(uint64_t segment_id, uint64_t start_sample, uint64_t end_sample); void min_max_changed(float min, float max); private Q_SLOTS: void on_samples_cleared(); void on_samples_added(QObject* segment, uint64_t start_sample, uint64_t end_sample); void on_min_max_changed(float min, float max); void on_capture_state_changed(int state); void on_delayed_conversion_start(); protected: shared_ptr channel_; ChannelType channel_type_; shared_ptr data_; shared_ptr converted_data_; ConversionType conversion_type_; map conversion_options_; float min_value_, max_value_; std::thread conversion_thread_; atomic conversion_interrupt_; mutex conversion_input_mutex_; condition_variable conversion_input_cond_; QTimer delayed_conversion_starter_; QString internal_name_, name_; QColor color_, bgcolor_; }; } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_SIGNALBASE_HPP pulseview-0.4.1/pv/data/decodesignal.hpp000600 001750 001750 00000014476 13365561737 017715 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2017 Soeren Apel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DATA_DECODESIGNAL_HPP #define PULSEVIEW_PV_DATA_DECODESIGNAL_HPP #include #include #include #include #include #include #include #include #include #include #include using std::atomic; using std::condition_variable; using std::map; using std::mutex; using std::pair; using std::vector; using std::shared_ptr; namespace pv { class Session; namespace data { namespace decode { class Annotation; class Decoder; class Row; } class Logic; class LogicSegment; class SignalBase; class SignalData; struct DecodeChannel { uint16_t id; ///< Global numerical ID for the decode channels in the stack uint16_t bit_id; ///< Tells which bit within a sample represents this channel const bool is_optional; const pv::data::SignalBase *assigned_signal; const QString name, desc; int initial_pin_state; const shared_ptr decoder_; const srd_channel *pdch_; }; struct DecodeSegment { map annotation_rows; pv::util::Timestamp start_time; double samplerate; int64_t samples_decoded_incl, samples_decoded_excl; }; class DecodeSignal : public SignalBase { Q_OBJECT private: static const double DecodeMargin; static const double DecodeThreshold; static const int64_t DecodeChunkLength; public: DecodeSignal(pv::Session &session); virtual ~DecodeSignal(); bool is_decode_signal() const; const vector< shared_ptr >& decoder_stack() const; void stack_decoder(const srd_decoder *decoder); void remove_decoder(int index); bool toggle_decoder_visibility(int index); void reset_decode(bool shutting_down = false); void begin_decode(); void pause_decode(); void resume_decode(); bool is_paused() const; QString error_message() const; const vector get_channels() const; void auto_assign_signals(const shared_ptr dec); void assign_signal(const uint16_t channel_id, const SignalBase *signal); int get_assigned_signal_count() const; void set_initial_pin_state(const uint16_t channel_id, const int init_state); double samplerate() const; const pv::util::Timestamp start_time() const; /** * Returns the number of samples that can be worked on, * i.e. the number of samples where samples are available * for all connected channels. */ int64_t get_working_sample_count(uint32_t segment_id) const; /** * Returns the number of processed samples. Newly generated annotations will * have sample numbers greater than this. * * If include_processing is true, this number will include the ones being * currently processed (in case the decoder stack is running). In this case, * newly generated annotations will have sample numbers smaller than this. */ int64_t get_decoded_sample_count(uint32_t segment_id, bool include_processing) const; vector visible_rows() const; /** * Extracts annotations from a single row into a vector. * Note: The annotations may be unsorted and only annotations that fully * fit into the sample range are considered. */ void get_annotation_subset( vector &dest, const decode::Row &row, uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const; /** * Extracts annotations from all rows into a vector. * Note: The annotations may be unsorted and only annotations that fully * fit into the sample range are considered. */ void get_annotation_subset( vector &dest, uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const; virtual void save_settings(QSettings &settings) const; virtual void restore_settings(QSettings &settings); private: void set_error_message(QString msg); uint32_t get_input_segment_count() const; uint32_t get_input_samplerate(uint32_t segment_id) const; void update_channel_list(); void commit_decoder_channels(); void mux_logic_samples(uint32_t segment_id, const int64_t start, const int64_t end); void logic_mux_proc(); void decode_data(const int64_t abs_start_samplenum, const int64_t sample_count, const shared_ptr input_segment); void decode_proc(); void start_srd_session(); void terminate_srd_session(); void stop_srd_session(); void connect_input_notifiers(); void create_decode_segment(); static void annotation_callback(srd_proto_data *pdata, void *decode_signal); Q_SIGNALS: void new_annotations(); // TODO Supply segment for which they belong to void decode_reset(); void decode_finished(); void channels_updated(); private Q_SLOTS: void on_capture_state_changed(int state); void on_data_cleared(); void on_data_received(); private: pv::Session &session_; vector channels_; struct srd_session *srd_session_; shared_ptr logic_mux_data_; uint32_t logic_mux_unit_size_; bool logic_mux_data_invalid_; vector< shared_ptr > stack_; bool stack_config_changed_; map, decode::Row> class_rows_; vector segments_; uint32_t current_segment_id_; mutable mutex input_mutex_, output_mutex_, decode_pause_mutex_, logic_mux_mutex_; mutable condition_variable decode_input_cond_, decode_pause_cond_, logic_mux_cond_; std::thread decode_thread_, logic_mux_thread_; atomic decode_interrupt_, logic_mux_interrupt_; bool decode_paused_; QString error_message_; }; } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_DECODESIGNAL_HPP pulseview-0.4.1/pv/data/logicsegment.cpp000600 001750 001750 00000044501 13365561737 017737 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "config.h" // For HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS #include #include #include #include #include #include #include "logic.hpp" #include "logicsegment.hpp" #include using std::lock_guard; using std::recursive_mutex; using std::max; using std::min; using std::shared_ptr; using std::vector; using sigrok::Logic; namespace pv { namespace data { const int LogicSegment::MipMapScalePower = 4; const int LogicSegment::MipMapScaleFactor = 1 << MipMapScalePower; const float LogicSegment::LogMipMapScaleFactor = logf(MipMapScaleFactor); const uint64_t LogicSegment::MipMapDataUnit = 64 * 1024; // bytes LogicSegment::LogicSegment(pv::data::Logic& owner, uint32_t segment_id, unsigned int unit_size, uint64_t samplerate) : Segment(segment_id, samplerate, unit_size), owner_(owner), last_append_sample_(0), last_append_accumulator_(0), last_append_extra_(0) { memset(mip_map_, 0, sizeof(mip_map_)); } LogicSegment::~LogicSegment() { lock_guard lock(mutex_); for (MipMapLevel &l : mip_map_) free(l.data); } template void LogicSegment::downsampleTmain(const T*&in, T &acc, T &prev) { // Accumulate one sample at a time for (uint64_t i = 0; i < MipMapScaleFactor; i++) { T sample = *in++; acc |= prev ^ sample; prev = sample; } } template <> void LogicSegment::downsampleTmain(const uint8_t*&in, uint8_t &acc, uint8_t &prev) { // Handle 8 bit samples in 32 bit steps uint32_t prev32 = prev | prev << 8 | prev << 16 | prev << 24; uint32_t acc32 = acc; const uint32_t *in32 = (const uint32_t*)in; for (uint64_t i = 0; i < MipMapScaleFactor; i += 4) { uint32_t sample32 = *in32++; acc32 |= prev32 ^ sample32; prev32 = sample32; } // Reduce result back to uint8_t #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ prev = (prev32 >> 24) & 0xff; // MSB is last #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ prev = prev32 & 0xff; // LSB is last #else #error Endianness unknown #endif acc |= acc32 & 0xff; acc |= (acc32 >> 8) & 0xff; acc |= (acc32 >> 16) & 0xff; acc |= (acc32 >> 24) & 0xff; in = (const uint8_t*)in32; } template <> void LogicSegment::downsampleTmain(const uint16_t*&in, uint16_t &acc, uint16_t &prev) { // Handle 16 bit samples in 32 bit steps uint32_t prev32 = prev | prev << 16; uint32_t acc32 = acc; const uint32_t *in32 = (const uint32_t*)in; for (uint64_t i = 0; i < MipMapScaleFactor; i += 2) { uint32_t sample32 = *in32++; acc32 |= prev32 ^ sample32; prev32 = sample32; } // Reduce result back to uint16_t #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ prev = (prev32 >> 16) & 0xffff; // MSB is last #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ prev = prev32 & 0xffff; // LSB is last #else #error Endian unknown #endif acc |= acc32 & 0xffff; acc |= (acc32 >> 16) & 0xffff; in = (const uint16_t*)in32; } template void LogicSegment::downsampleT(const uint8_t *in_, uint8_t *&out_, uint64_t len) { const T *in = (const T*)in_; T *out = (T*)out_; T prev = last_append_sample_; T acc = last_append_accumulator_; // Try to complete the previous downsample if (last_append_extra_) { while (last_append_extra_ < MipMapScaleFactor && len > 0) { T sample = *in++; acc |= prev ^ sample; prev = sample; last_append_extra_++; len--; } if (!len) { // Not enough samples available to complete downsample last_append_sample_ = prev; last_append_accumulator_ = acc; return; } // We have a complete downsample *out++ = acc; acc = 0; last_append_extra_ = 0; } // Handle complete blocks of MipMapScaleFactor samples while (len >= MipMapScaleFactor) { downsampleTmain(in, acc, prev); len -= MipMapScaleFactor; // Output downsample *out++ = acc; acc = 0; } // Process remainder, not enough for a complete sample while (len > 0) { T sample = *in++; acc |= prev ^ sample; prev = sample; last_append_extra_++; len--; } // Update context last_append_sample_ = prev; last_append_accumulator_ = acc; out_ = (uint8_t *)out; } void LogicSegment::downsampleGeneric(const uint8_t *in, uint8_t *&out, uint64_t len) { // Downsample using the generic unpack_sample() // which can handle any width between 1 and 8 bytes uint64_t prev = last_append_sample_; uint64_t acc = last_append_accumulator_; // Try to complete the previous downsample if (last_append_extra_) { while (last_append_extra_ < MipMapScaleFactor && len > 0) { const uint64_t sample = unpack_sample(in); in += unit_size_; acc |= prev ^ sample; prev = sample; last_append_extra_++; len--; } if (!len) { // Not enough samples available to complete downsample last_append_sample_ = prev; last_append_accumulator_ = acc; return; } // We have a complete downsample pack_sample(out, acc); out += unit_size_; acc = 0; last_append_extra_ = 0; } // Handle complete blocks of MipMapScaleFactor samples while (len >= MipMapScaleFactor) { // Accumulate one sample at a time for (uint64_t i = 0; i < MipMapScaleFactor; i++) { const uint64_t sample = unpack_sample(in); in += unit_size_; acc |= prev ^ sample; prev = sample; } len -= MipMapScaleFactor; // Output downsample pack_sample(out, acc); out += unit_size_; acc = 0; } // Process remainder, not enough for a complete sample while (len > 0) { const uint64_t sample = unpack_sample(in); in += unit_size_; acc |= prev ^ sample; prev = sample; last_append_extra_++; len--; } // Update context last_append_sample_ = prev; last_append_accumulator_ = acc; } inline uint64_t LogicSegment::unpack_sample(const uint8_t *ptr) const { #ifdef HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS return *(uint64_t*)ptr; #else uint64_t value = 0; switch (unit_size_) { default: value |= ((uint64_t)ptr[7]) << 56; /* FALLTHRU */ case 7: value |= ((uint64_t)ptr[6]) << 48; /* FALLTHRU */ case 6: value |= ((uint64_t)ptr[5]) << 40; /* FALLTHRU */ case 5: value |= ((uint64_t)ptr[4]) << 32; /* FALLTHRU */ case 4: value |= ((uint32_t)ptr[3]) << 24; /* FALLTHRU */ case 3: value |= ((uint32_t)ptr[2]) << 16; /* FALLTHRU */ case 2: value |= ptr[1] << 8; /* FALLTHRU */ case 1: value |= ptr[0]; /* FALLTHRU */ case 0: break; } return value; #endif } inline void LogicSegment::pack_sample(uint8_t *ptr, uint64_t value) { #ifdef HAVE_UNALIGNED_LITTLE_ENDIAN_ACCESS *(uint64_t*)ptr = value; #else switch (unit_size_) { default: ptr[7] = value >> 56; /* FALLTHRU */ case 7: ptr[6] = value >> 48; /* FALLTHRU */ case 6: ptr[5] = value >> 40; /* FALLTHRU */ case 5: ptr[4] = value >> 32; /* FALLTHRU */ case 4: ptr[3] = value >> 24; /* FALLTHRU */ case 3: ptr[2] = value >> 16; /* FALLTHRU */ case 2: ptr[1] = value >> 8; /* FALLTHRU */ case 1: ptr[0] = value; /* FALLTHRU */ case 0: break; } #endif } void LogicSegment::append_payload(shared_ptr logic) { assert(unit_size_ == logic->unit_size()); assert((logic->data_length() % unit_size_) == 0); append_payload(logic->data_pointer(), logic->data_length()); } void LogicSegment::append_payload(void *data, uint64_t data_size) { assert((data_size % unit_size_) == 0); lock_guard lock(mutex_); const uint64_t prev_sample_count = sample_count_; const uint64_t sample_count = data_size / unit_size_; append_samples(data, sample_count); // Generate the first mip-map from the data append_payload_to_mipmap(); if (sample_count > 1) owner_.notify_samples_added(this, prev_sample_count + 1, prev_sample_count + 1 + sample_count); else owner_.notify_samples_added(this, prev_sample_count + 1, prev_sample_count + 1); } void LogicSegment::get_samples(int64_t start_sample, int64_t end_sample, uint8_t* dest) const { assert(start_sample >= 0); assert(start_sample <= (int64_t)sample_count_); assert(end_sample >= 0); assert(end_sample <= (int64_t)sample_count_); assert(start_sample <= end_sample); assert(dest != nullptr); lock_guard lock(mutex_); get_raw_samples(start_sample, (end_sample - start_sample), dest); } void LogicSegment::get_subsampled_edges( vector &edges, uint64_t start, uint64_t end, float min_length, int sig_index, bool first_change_only) { uint64_t index = start; unsigned int level; bool last_sample; bool fast_forward; assert(start <= end); assert(min_length > 0); assert(sig_index >= 0); assert(sig_index < 64); lock_guard lock(mutex_); // Make sure we only process as many samples as we have if (end > get_sample_count()) end = get_sample_count(); const uint64_t block_length = (uint64_t)max(min_length, 1.0f); const unsigned int min_level = max((int)floorf(logf(min_length) / LogMipMapScaleFactor) - 1, 0); const uint64_t sig_mask = 1ULL << sig_index; // Store the initial state last_sample = (get_unpacked_sample(start) & sig_mask) != 0; if (!first_change_only) edges.emplace_back(index++, last_sample); while (index + block_length <= end) { //----- Continue to search -----// level = min_level; // We cannot fast-forward if there is no mip-map data at // the minimum level. fast_forward = (mip_map_[level].data != nullptr); if (min_length < MipMapScaleFactor) { // Search individual samples up to the beginning of // the next first level mip map block const uint64_t final_index = min(end, pow2_ceil(index, MipMapScalePower)); for (; index < final_index && (index & ~((uint64_t)(~0) << MipMapScalePower)) != 0; index++) { const bool sample = (get_unpacked_sample(index) & sig_mask) != 0; // If there was a change we cannot fast forward if (sample != last_sample) { fast_forward = false; break; } } } else { // If resolution is less than a mip map block, // round up to the beginning of the mip-map block // for this level of detail const int min_level_scale_power = (level + 1) * MipMapScalePower; index = pow2_ceil(index, min_level_scale_power); if (index >= end) break; // We can fast forward only if there was no change const bool sample = (get_unpacked_sample(index) & sig_mask) != 0; if (last_sample != sample) fast_forward = false; } if (fast_forward) { // Fast forward: This involves zooming out to higher // levels of the mip map searching for changes, then // zooming in on them to find the point where the edge // begins. // Slide right and zoom out at the beginnings of mip-map // blocks until we encounter a change while (true) { const int level_scale_power = (level + 1) * MipMapScalePower; const uint64_t offset = index >> level_scale_power; // Check if we reached the last block at this // level, or if there was a change in this block if (offset >= mip_map_[level].length || (get_subsample(level, offset) & sig_mask)) break; if ((offset & ~((uint64_t)(~0) << MipMapScalePower)) == 0) { // If we are now at the beginning of a // higher level mip-map block ascend one // level if ((level + 1 >= ScaleStepCount) || (!mip_map_[level + 1].data)) break; level++; } else { // Slide right to the beginning of the // next mip map block index = pow2_ceil(index + 1, level_scale_power); } } // Zoom in, and slide right until we encounter a change, // and repeat until we reach min_level while (true) { assert(mip_map_[level].data); const int level_scale_power = (level + 1) * MipMapScalePower; const uint64_t offset = index >> level_scale_power; // Check if we reached the last block at this // level, or if there was a change in this block if (offset >= mip_map_[level].length || (get_subsample(level, offset) & sig_mask)) { // Zoom in unless we reached the minimum // zoom if (level == min_level) break; level--; } else { // Slide right to the beginning of the // next mip map block index = pow2_ceil(index + 1, level_scale_power); } } // If individual samples within the limit of resolution, // do a linear search for the next transition within the // block if (min_length < MipMapScaleFactor) { for (; index < end; index++) { const bool sample = (get_unpacked_sample(index) & sig_mask) != 0; if (sample != last_sample) break; } } } //----- Store the edge -----// // Take the last sample of the quanization block const int64_t final_index = index + block_length; if (index + block_length > end) break; // Store the final state const bool final_sample = (get_unpacked_sample(final_index - 1) & sig_mask) != 0; edges.emplace_back(index, final_sample); index = final_index; last_sample = final_sample; if (first_change_only) break; } // Add the final state if (!first_change_only) { const bool end_sample = get_unpacked_sample(end) & sig_mask; if (last_sample != end_sample) edges.emplace_back(end, end_sample); edges.emplace_back(end + 1, end_sample); } } void LogicSegment::get_surrounding_edges(vector &dest, uint64_t origin_sample, float min_length, int sig_index) { if (origin_sample >= sample_count_) return; // Put the edges vector on the heap, it can become quite big until we can // use a get_subsampled_edges() implementation that searches backwards vector* edges = new vector; // Get all edges to the left of origin_sample get_subsampled_edges(*edges, 0, origin_sample, min_length, sig_index, false); // If we don't specify "first only", the first and last edge are the states // at samples 0 and origin_sample. If only those exist, there are no edges if (edges->size() == 2) { delete edges; return; } // Dismiss the entry for origin_sample so that back() gives us the // real last entry edges->pop_back(); dest.push_back(edges->back()); edges->clear(); // Get first edge to the right of origin_sample get_subsampled_edges(*edges, origin_sample, sample_count_, min_length, sig_index, true); // "first only" is specified, so nothing needs to be dismissed if (edges->size() == 0) { delete edges; return; } dest.push_back(edges->front()); delete edges; } void LogicSegment::reallocate_mipmap_level(MipMapLevel &m) { lock_guard lock(mutex_); const uint64_t new_data_length = ((m.length + MipMapDataUnit - 1) / MipMapDataUnit) * MipMapDataUnit; if (new_data_length > m.data_length) { m.data_length = new_data_length; // Padding is added to allow for the uint64_t write word m.data = realloc(m.data, new_data_length * unit_size_ + sizeof(uint64_t)); } } void LogicSegment::append_payload_to_mipmap() { MipMapLevel &m0 = mip_map_[0]; uint64_t prev_length; uint8_t *dest_ptr; SegmentDataIterator* it; uint64_t accumulator; unsigned int diff_counter; // Expand the data buffer to fit the new samples prev_length = m0.length; m0.length = sample_count_ / MipMapScaleFactor; // Break off if there are no new samples to compute if (m0.length == prev_length) return; reallocate_mipmap_level(m0); dest_ptr = (uint8_t*)m0.data + prev_length * unit_size_; // Iterate through the samples to populate the first level mipmap const uint64_t start_sample = prev_length * MipMapScaleFactor; const uint64_t end_sample = m0.length * MipMapScaleFactor; uint64_t len_sample = end_sample - start_sample; it = begin_sample_iteration(start_sample); while (len_sample > 0) { // Number of samples available in this chunk uint64_t count = get_iterator_valid_length(it); // Reduce if less than asked for count = std::min(count, len_sample); uint8_t *src_ptr = get_iterator_value(it); // Submit these contiguous samples to downsampling in bulk if (unit_size_ == 1) downsampleT(src_ptr, dest_ptr, count); else if (unit_size_ == 2) downsampleT(src_ptr, dest_ptr, count); else if (unit_size_ == 4) downsampleT(src_ptr, dest_ptr, count); else if (unit_size_ == 8) downsampleT(src_ptr, dest_ptr, count); else downsampleGeneric(src_ptr, dest_ptr, count); len_sample -= count; // Advance iterator, should move to start of next chunk continue_sample_iteration(it, count); } end_sample_iteration(it); // Compute higher level mipmaps for (unsigned int level = 1; level < ScaleStepCount; level++) { MipMapLevel &m = mip_map_[level]; const MipMapLevel &ml = mip_map_[level - 1]; // Expand the data buffer to fit the new samples prev_length = m.length; m.length = ml.length / MipMapScaleFactor; // Break off if there are no more samples to be computed if (m.length == prev_length) break; reallocate_mipmap_level(m); // Subsample the lower level const uint8_t* src_ptr = (uint8_t*)ml.data + unit_size_ * prev_length * MipMapScaleFactor; const uint8_t *const end_dest_ptr = (uint8_t*)m.data + unit_size_ * m.length; for (dest_ptr = (uint8_t*)m.data + unit_size_ * prev_length; dest_ptr < end_dest_ptr; dest_ptr += unit_size_) { accumulator = 0; diff_counter = MipMapScaleFactor; while (diff_counter-- > 0) { accumulator |= unpack_sample(src_ptr); src_ptr += unit_size_; } pack_sample(dest_ptr, accumulator); } } } uint64_t LogicSegment::get_unpacked_sample(uint64_t index) const { assert(index < sample_count_); assert(unit_size_ <= 8); // 8 * 8 = 64 channels uint8_t data[8]; get_raw_samples(index, 1, data); return unpack_sample(data); } uint64_t LogicSegment::get_subsample(int level, uint64_t offset) const { assert(level >= 0); assert(mip_map_[level].data); return unpack_sample((uint8_t*)mip_map_[level].data + unit_size_ * offset); } uint64_t LogicSegment::pow2_ceil(uint64_t x, unsigned int power) { const uint64_t p = UINT64_C(1) << power; return (x + p - 1) / p * p; } } // namespace data } // namespace pv pulseview-0.4.1/pv/data/analog.cpp000600 001750 001750 00000004021 13365561737 016511 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include "analog.hpp" #include "analogsegment.hpp" using std::deque; using std::max; using std::shared_ptr; using std::vector; namespace pv { namespace data { Analog::Analog() : SignalData() { } void Analog::push_segment(shared_ptr &segment) { segments_.push_back(segment); } const deque< shared_ptr >& Analog::analog_segments() const { return segments_; } vector< shared_ptr > Analog::segments() const { return vector< shared_ptr >( segments_.begin(), segments_.end()); } uint32_t Analog::get_segment_count() const { return (uint32_t)segments_.size(); } void Analog::clear() { segments_.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; for (const shared_ptr& s : segments_) { assert(s); l = max(l, s->get_sample_count()); } return l; } void Analog::notify_samples_added(QObject* segment, uint64_t start_sample, uint64_t end_sample) { samples_added(segment, start_sample, end_sample); } void Analog::notify_min_max_changed(float min, float max) { min_max_changed(min, max); } } // namespace data } // namespace pv pulseview-0.4.1/pv/data/analogsegment.hpp000600 001750 001750 00000004611 13365561737 020106 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DATA_ANALOGSEGMENT_HPP #define PULSEVIEW_PV_DATA_ANALOGSEGMENT_HPP #include "segment.hpp" #include #include #include using std::pair; namespace AnalogSegmentTest { struct Basic; } namespace pv { namespace data { class Analog; class AnalogSegment : public Segment { Q_OBJECT public: struct EnvelopeSample { float min; float max; }; struct EnvelopeSection { uint64_t start; unsigned int scale; uint64_t length; EnvelopeSample *samples; }; private: struct Envelope { uint64_t length; uint64_t data_length; EnvelopeSample *samples; }; private: static const unsigned int ScaleStepCount = 10; static const int EnvelopeScalePower; static const int EnvelopeScaleFactor; static const float LogEnvelopeScaleFactor; static const uint64_t EnvelopeDataUnit; public: AnalogSegment(Analog& owner, uint32_t segment_id, uint64_t samplerate); virtual ~AnalogSegment(); void append_interleaved_samples(const float *data, size_t sample_count, size_t stride); void get_samples(int64_t start_sample, int64_t end_sample, float* dest) const; const pair get_min_max() const; float* get_iterator_value_ptr(SegmentDataIterator* it); void get_envelope_section(EnvelopeSection &s, uint64_t start, uint64_t end, float min_length) const; private: void reallocate_envelope(Envelope &e); void append_payload_to_envelope_levels(); private: Analog& owner_; struct Envelope envelope_levels_[ScaleStepCount]; float min_value_, max_value_; friend struct AnalogSegmentTest::Basic; }; } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_ANALOGSEGMENT_HPP pulseview-0.4.1/pv/data/logic.hpp000600 001750 001750 00000003452 13365561737 016361 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DATA_LOGIC_HPP #define PULSEVIEW_PV_DATA_LOGIC_HPP #include "signaldata.hpp" #include #include using std::deque; using std::shared_ptr; using std::vector; namespace pv { namespace data { class LogicSegment; class Logic : public SignalData { Q_OBJECT public: Logic(unsigned int num_channels); unsigned int num_channels() const; void push_segment(shared_ptr &segment); const deque< shared_ptr >& logic_segments() const; vector< shared_ptr > segments() const; uint32_t get_segment_count() const; void clear(); double get_samplerate() const; uint64_t max_sample_count() const; void notify_samples_added(QObject* segment, uint64_t start_sample, uint64_t end_sample); Q_SIGNALS: void samples_cleared(); void samples_added(QObject* segment, uint64_t start_sample, uint64_t end_sample); private: const unsigned int num_channels_; deque< shared_ptr > segments_; }; } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_LOGIC_HPP pulseview-0.4.1/pv/data/signaldata.hpp000600 001750 001750 00000002670 13365561737 017374 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DATA_SIGNALDATA_HPP #define PULSEVIEW_PV_DATA_SIGNALDATA_HPP #include #include #include #include using std::shared_ptr; using std::vector; namespace pv { namespace data { class Segment; class SignalData : public QObject { Q_OBJECT public: SignalData() = default; virtual ~SignalData() = default; public: virtual vector< shared_ptr > segments() const = 0; virtual uint32_t get_segment_count() const = 0; virtual void clear() = 0; virtual uint64_t max_sample_count() const = 0; virtual double get_samplerate() const = 0; }; } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_SIGNALDATA_HPP pulseview-0.4.1/pv/data/segment.cpp000600 001750 001750 00000016712 13365561737 016724 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2017 Soeren Apel * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "segment.hpp" #include #include #include #include using std::bad_alloc; using std::lock_guard; using std::min; using std::recursive_mutex; namespace pv { namespace data { const uint64_t Segment::MaxChunkSize = 10 * 1024 * 1024; /* 10MiB */ Segment::Segment(uint32_t segment_id, uint64_t samplerate, unsigned int unit_size) : segment_id_(segment_id), sample_count_(0), start_time_(0), samplerate_(samplerate), unit_size_(unit_size), iterator_count_(0), mem_optimization_requested_(false), is_complete_(false) { lock_guard lock(mutex_); assert(unit_size_ > 0); // Determine the number of samples we can fit in one chunk // without exceeding MaxChunkSize chunk_size_ = min(MaxChunkSize, (MaxChunkSize / unit_size_) * unit_size_); // Create the initial chunk current_chunk_ = new uint8_t[chunk_size_ + 7]; /* FIXME +7 is workaround for #1284 */ data_chunks_.push_back(current_chunk_); used_samples_ = 0; unused_samples_ = chunk_size_ / unit_size_; } Segment::~Segment() { lock_guard lock(mutex_); for (uint8_t* chunk : data_chunks_) delete[] chunk; } uint64_t Segment::get_sample_count() const { lock_guard lock(mutex_); return sample_count_; } const pv::util::Timestamp& Segment::start_time() const { return start_time_; } double Segment::samplerate() const { return samplerate_; } void Segment::set_samplerate(double samplerate) { samplerate_ = samplerate; } unsigned int Segment::unit_size() const { return unit_size_; } uint32_t Segment::segment_id() const { return segment_id_; } void Segment::set_complete() { is_complete_ = true; } bool Segment::is_complete() const { return is_complete_; } void Segment::free_unused_memory() { lock_guard lock(mutex_); // Do not mess with the data chunks if we have iterators pointing at them if (iterator_count_ > 0) { mem_optimization_requested_ = true; return; } if (current_chunk_) { // No more data will come in, so re-create the last chunk accordingly uint8_t* resized_chunk = new uint8_t[used_samples_ * unit_size_ + 7]; /* FIXME +7 is workaround for #1284 */ memcpy(resized_chunk, current_chunk_, used_samples_ * unit_size_); delete[] current_chunk_; current_chunk_ = resized_chunk; data_chunks_.pop_back(); data_chunks_.push_back(resized_chunk); } } void Segment::append_single_sample(void *data) { lock_guard lock(mutex_); // There will always be space for at least one sample in // the current chunk, so we do not need to test for space memcpy(current_chunk_ + (used_samples_ * unit_size_), data, unit_size_); used_samples_++; unused_samples_--; if (unused_samples_ == 0) { current_chunk_ = new uint8_t[chunk_size_ + 7]; /* FIXME +7 is workaround for #1284 */ data_chunks_.push_back(current_chunk_); used_samples_ = 0; unused_samples_ = chunk_size_ / unit_size_; } sample_count_++; } void Segment::append_samples(void* data, uint64_t samples) { lock_guard lock(mutex_); const uint8_t* data_byte_ptr = (uint8_t*)data; uint64_t remaining_samples = samples; uint64_t data_offset = 0; do { uint64_t copy_count = 0; if (remaining_samples <= unused_samples_) { // All samples fit into the current chunk copy_count = remaining_samples; } else { // Only a part of the samples fit, fill up current chunk copy_count = unused_samples_; } const uint8_t* dest = &(current_chunk_[used_samples_ * unit_size_]); const uint8_t* src = &(data_byte_ptr[data_offset]); memcpy((void*)dest, (void*)src, (copy_count * unit_size_)); used_samples_ += copy_count; unused_samples_ -= copy_count; remaining_samples -= copy_count; data_offset += (copy_count * unit_size_); if (unused_samples_ == 0) { try { // If we're out of memory, allocating a chunk will throw // std::bad_alloc. To give the application some usable memory // to work with in case chunk allocation fails, we allocate // extra memory and throw it away if it all succeeded. // This way, memory allocation will fail early enough to let // PV remain alive. Otherwise, PV will crash in a random // memory-allocating part of the application. current_chunk_ = new uint8_t[chunk_size_ + 7]; /* FIXME +7 is workaround for #1284 */ const int dummy_size = 2 * chunk_size_; auto dummy_chunk = new uint8_t[dummy_size]; memset(dummy_chunk, 0xFF, dummy_size); delete[] dummy_chunk; } catch (bad_alloc&) { delete[] current_chunk_; // The new may have succeeded current_chunk_ = nullptr; throw; } data_chunks_.push_back(current_chunk_); used_samples_ = 0; unused_samples_ = chunk_size_ / unit_size_; } } while (remaining_samples > 0); sample_count_ += samples; } void Segment::get_raw_samples(uint64_t start, uint64_t count, uint8_t* dest) const { assert(start < sample_count_); assert(start + count <= sample_count_); assert(count > 0); assert(dest != nullptr); lock_guard lock(mutex_); uint8_t* dest_ptr = dest; uint64_t chunk_num = (start * unit_size_) / chunk_size_; uint64_t chunk_offs = (start * unit_size_) % chunk_size_; while (count > 0) { const uint8_t* chunk = data_chunks_[chunk_num]; uint64_t copy_size = min(count * unit_size_, chunk_size_ - chunk_offs); memcpy(dest_ptr, chunk + chunk_offs, copy_size); dest_ptr += copy_size; count -= (copy_size / unit_size_); chunk_num++; chunk_offs = 0; } } SegmentDataIterator* Segment::begin_sample_iteration(uint64_t start) { SegmentDataIterator* it = new SegmentDataIterator; assert(start < sample_count_); iterator_count_++; it->sample_index = start; it->chunk_num = (start * unit_size_) / chunk_size_; it->chunk_offs = (start * unit_size_) % chunk_size_; it->chunk = data_chunks_[it->chunk_num]; return it; } void Segment::continue_sample_iteration(SegmentDataIterator* it, uint64_t increase) { it->sample_index += increase; it->chunk_offs += (increase * unit_size_); if (it->chunk_offs > (chunk_size_ - 1)) { it->chunk_num++; it->chunk_offs -= chunk_size_; it->chunk = data_chunks_[it->chunk_num]; } } void Segment::end_sample_iteration(SegmentDataIterator* it) { delete it; iterator_count_--; if ((iterator_count_ == 0) && mem_optimization_requested_) { mem_optimization_requested_ = false; free_unused_memory(); } } uint8_t* Segment::get_iterator_value(SegmentDataIterator* it) { assert(it->sample_index <= (sample_count_ - 1)); return (it->chunk + it->chunk_offs); } uint64_t Segment::get_iterator_valid_length(SegmentDataIterator* it) { assert(it->sample_index <= (sample_count_ - 1)); return ((chunk_size_ - it->chunk_offs) / unit_size_); } } // namespace data } // namespace pv pulseview-0.4.1/pv/data/signaldata.cpp000600 001750 001750 00000001545 13365561737 017367 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "signaldata.hpp" namespace pv { namespace data { } // namespace data } // namespace pv pulseview-0.4.1/pv/data/analogsegment.cpp000600 001750 001750 00000016460 13365561737 020106 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include "analog.hpp" #include "analogsegment.hpp" using std::lock_guard; using std::recursive_mutex; using std::make_pair; using std::max; using std::max_element; using std::min; using std::min_element; using std::pair; using std::unique_ptr; namespace pv { namespace data { const int AnalogSegment::EnvelopeScalePower = 4; const int AnalogSegment::EnvelopeScaleFactor = 1 << EnvelopeScalePower; const float AnalogSegment::LogEnvelopeScaleFactor = logf(EnvelopeScaleFactor); const uint64_t AnalogSegment::EnvelopeDataUnit = 64 * 1024; // bytes AnalogSegment::AnalogSegment(Analog& owner, uint32_t segment_id, uint64_t samplerate) : Segment(segment_id, samplerate, sizeof(float)), owner_(owner), min_value_(0), max_value_(0) { lock_guard lock(mutex_); memset(envelope_levels_, 0, sizeof(envelope_levels_)); } AnalogSegment::~AnalogSegment() { lock_guard lock(mutex_); for (Envelope &e : envelope_levels_) free(e.samples); } void AnalogSegment::append_interleaved_samples(const float *data, size_t sample_count, size_t stride) { assert(unit_size_ == sizeof(float)); lock_guard lock(mutex_); uint64_t prev_sample_count = sample_count_; // Deinterleave the samples and add them unique_ptr deint_data(new float[sample_count]); float *deint_data_ptr = deint_data.get(); for (uint32_t i = 0; i < sample_count; i++) { *deint_data_ptr = (float)(*data); deint_data_ptr++; data += stride; } append_samples(deint_data.get(), sample_count); // Generate the first mip-map from the data append_payload_to_envelope_levels(); if (sample_count > 1) owner_.notify_samples_added(this, prev_sample_count + 1, prev_sample_count + 1 + sample_count); else owner_.notify_samples_added(this, prev_sample_count + 1, prev_sample_count + 1); } void AnalogSegment::get_samples(int64_t start_sample, int64_t end_sample, float* dest) const { assert(start_sample >= 0); assert(start_sample < (int64_t)sample_count_); assert(end_sample >= 0); assert(end_sample <= (int64_t)sample_count_); assert(start_sample <= end_sample); assert(dest != nullptr); lock_guard lock(mutex_); get_raw_samples(start_sample, (end_sample - start_sample), (uint8_t*)dest); } const pair AnalogSegment::get_min_max() const { return make_pair(min_value_, max_value_); } float* AnalogSegment::get_iterator_value_ptr(SegmentDataIterator* it) { assert(it->sample_index <= (sample_count_ - 1)); return (float*)(it->chunk + it->chunk_offs); } void AnalogSegment::get_envelope_section(EnvelopeSection &s, uint64_t start, uint64_t end, float min_length) const { assert(end <= get_sample_count()); assert(start <= end); assert(min_length > 0); lock_guard lock(mutex_); const unsigned int min_level = max((int)floorf(logf(min_length) / LogEnvelopeScaleFactor) - 1, 0); const unsigned int scale_power = (min_level + 1) * EnvelopeScalePower; start >>= scale_power; end >>= scale_power; s.start = start << scale_power; s.scale = 1 << scale_power; s.length = end - start; s.samples = new EnvelopeSample[s.length]; memcpy(s.samples, envelope_levels_[min_level].samples + start, s.length * sizeof(EnvelopeSample)); } void AnalogSegment::reallocate_envelope(Envelope &e) { const uint64_t new_data_length = ((e.length + EnvelopeDataUnit - 1) / EnvelopeDataUnit) * EnvelopeDataUnit; if (new_data_length > e.data_length) { e.data_length = new_data_length; e.samples = (EnvelopeSample*)realloc(e.samples, new_data_length * sizeof(EnvelopeSample)); } } void AnalogSegment::append_payload_to_envelope_levels() { Envelope &e0 = envelope_levels_[0]; uint64_t prev_length; EnvelopeSample *dest_ptr; SegmentDataIterator* it; // Expand the data buffer to fit the new samples prev_length = e0.length; e0.length = sample_count_ / EnvelopeScaleFactor; // Calculate min/max values in case we have too few samples for an envelope const float old_min_value = min_value_, old_max_value = max_value_; if (sample_count_ < EnvelopeScaleFactor) { it = begin_sample_iteration(0); for (uint64_t i = 0; i < sample_count_; i++) { const float sample = *get_iterator_value_ptr(it); if (sample < min_value_) min_value_ = sample; if (sample > max_value_) max_value_ = sample; continue_sample_iteration(it, 1); } end_sample_iteration(it); } // Break off if there are no new samples to compute if (e0.length == prev_length) return; reallocate_envelope(e0); dest_ptr = e0.samples + prev_length; // Iterate through the samples to populate the first level mipmap uint64_t start_sample = prev_length * EnvelopeScaleFactor; uint64_t end_sample = e0.length * EnvelopeScaleFactor; it = begin_sample_iteration(start_sample); for (uint64_t i = start_sample; i < end_sample; i += EnvelopeScaleFactor) { const float* samples = get_iterator_value_ptr(it); const EnvelopeSample sub_sample = { *min_element(samples, samples + EnvelopeScaleFactor), *max_element(samples, samples + EnvelopeScaleFactor), }; if (sub_sample.min < min_value_) min_value_ = sub_sample.min; if (sub_sample.max > max_value_) max_value_ = sub_sample.max; continue_sample_iteration(it, EnvelopeScaleFactor); *dest_ptr++ = sub_sample; } end_sample_iteration(it); // Compute higher level mipmaps for (unsigned int level = 1; level < ScaleStepCount; level++) { Envelope &e = envelope_levels_[level]; const Envelope &el = envelope_levels_[level - 1]; // Expand the data buffer to fit the new samples prev_length = e.length; e.length = el.length / EnvelopeScaleFactor; // Break off if there are no more samples to be computed if (e.length == prev_length) break; reallocate_envelope(e); // Subsample the lower level const EnvelopeSample *src_ptr = el.samples + prev_length * EnvelopeScaleFactor; const EnvelopeSample *const end_dest_ptr = e.samples + e.length; for (dest_ptr = e.samples + prev_length; dest_ptr < end_dest_ptr; dest_ptr++) { const EnvelopeSample *const end_src_ptr = src_ptr + EnvelopeScaleFactor; EnvelopeSample sub_sample = *src_ptr++; while (src_ptr < end_src_ptr) { sub_sample.min = min(sub_sample.min, src_ptr->min);; sub_sample.max = max(sub_sample.max, src_ptr->max); src_ptr++; } *dest_ptr = sub_sample; } } // Notify if the min or max value changed if ((old_min_value != min_value_) || (old_max_value != max_value_)) owner_.min_max_changed(min_value_, max_value_); } } // namespace data } // namespace pv pulseview-0.4.1/pv/data/logicsegment.hpp000600 001750 001750 00000007155 13365561737 017750 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DATA_LOGICSEGMENT_HPP #define PULSEVIEW_PV_DATA_LOGICSEGMENT_HPP #include "segment.hpp" #include #include #include using std::pair; using std::shared_ptr; using std::vector; namespace sigrok { class Logic; } namespace LogicSegmentTest { struct Pow2; struct Basic; struct LargeData; struct Pulses; struct LongPulses; } namespace pv { namespace data { class Logic; class LogicSegment : public Segment { Q_OBJECT public: typedef pair EdgePair; static const unsigned int ScaleStepCount = 10; static const int MipMapScalePower; static const int MipMapScaleFactor; static const float LogMipMapScaleFactor; static const uint64_t MipMapDataUnit; private: struct MipMapLevel { uint64_t length; uint64_t data_length; void *data; }; public: LogicSegment(pv::data::Logic& owner, uint32_t segment_id, unsigned int unit_size, uint64_t samplerate); virtual ~LogicSegment(); void append_payload(shared_ptr logic); void append_payload(void *data, uint64_t data_size); void get_samples(int64_t start_sample, int64_t end_sample, uint8_t* dest) const; /** * Parses a logic data segment to generate a list of transitions * in a time interval to a given level of detail. * @param[out] edges The vector to place the edges into. * @param[in] start The start sample index. * @param[in] end The end sample index. * @param[in] min_length The minimum number of samples that * can be resolved at this level of detail. * @param[in] sig_index The index of the signal. */ void get_subsampled_edges(vector &edges, uint64_t start, uint64_t end, float min_length, int sig_index, bool first_change_only = false); void get_surrounding_edges(vector &dest, uint64_t origin_sample, float min_length, int sig_index); private: uint64_t unpack_sample(const uint8_t *ptr) const; void pack_sample(uint8_t *ptr, uint64_t value); void reallocate_mipmap_level(MipMapLevel &m); void append_payload_to_mipmap(); uint64_t get_unpacked_sample(uint64_t index) const; template void downsampleTmain(const T*&in, T &acc, T &prev); template void downsampleT(const uint8_t *in, uint8_t *&out, uint64_t len); void downsampleGeneric(const uint8_t *in, uint8_t *&out, uint64_t len); private: uint64_t get_subsample(int level, uint64_t offset) const; static uint64_t pow2_ceil(uint64_t x, unsigned int power); private: Logic& owner_; struct MipMapLevel mip_map_[ScaleStepCount]; uint64_t last_append_sample_; uint64_t last_append_accumulator_; uint64_t last_append_extra_; friend struct LogicSegmentTest::Pow2; friend struct LogicSegmentTest::Basic; friend struct LogicSegmentTest::LargeData; friend struct LogicSegmentTest::Pulses; friend struct LogicSegmentTest::LongPulses; }; } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_LOGICSEGMENT_HPP pulseview-0.4.1/pv/data/logic.cpp000600 001750 001750 00000004073 13365561737 016354 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include "logic.hpp" #include "logicsegment.hpp" using std::deque; using std::max; using std::shared_ptr; using std::vector; namespace pv { namespace data { Logic::Logic(unsigned int num_channels) : SignalData(), num_channels_(num_channels) { assert(num_channels_ > 0); } unsigned int Logic::num_channels() const { return num_channels_; } void Logic::push_segment(shared_ptr &segment) { segments_.push_back(segment); } const deque< shared_ptr >& Logic::logic_segments() const { return segments_; } vector< shared_ptr > Logic::segments() const { return vector< shared_ptr >(segments_.begin(), segments_.end()); } uint32_t Logic::get_segment_count() const { return (uint32_t)segments_.size(); } void Logic::clear() { segments_.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; for (const shared_ptr& s : segments_) { assert(s); l = max(l, s->get_sample_count()); } return l; } void Logic::notify_samples_added(QObject* segment, uint64_t start_sample, uint64_t end_sample) { samples_added(segment, start_sample, end_sample); } } // namespace data } // namespace pv pulseview-0.4.1/pv/data/analog.hpp000600 001750 001750 00000003510 13365561737 016520 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DATA_ANALOG_HPP #define PULSEVIEW_PV_DATA_ANALOG_HPP #include "signaldata.hpp" #include #include #include using std::deque; using std::shared_ptr; using std::vector; namespace pv { namespace data { class AnalogSegment; class Analog : public SignalData { Q_OBJECT public: Analog(); void push_segment(shared_ptr &segment); const deque< shared_ptr >& analog_segments() const; vector< shared_ptr > segments() const; uint32_t get_segment_count() const; void clear(); double get_samplerate() const; uint64_t max_sample_count() const; void notify_samples_added(QObject* segment, uint64_t start_sample, uint64_t end_sample); void notify_min_max_changed(float min, float max); Q_SIGNALS: void samples_cleared(); void samples_added(QObject* segment, uint64_t start_sample, uint64_t end_sample); void min_max_changed(float min, float max); private: deque< shared_ptr > segments_; }; } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_ANALOG_HPP pulseview-0.4.1/pv/globalsettings.hpp000600 001750 001750 00000007376 13365561737 017405 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2017 Soeren Apel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_GLOBALSETTINGS_HPP #define PULSEVIEW_GLOBALSETTINGS_HPP #include #include #include #include #include #include #include using std::map; using std::pair; using std::vector; namespace pv { extern const vector< pair > Themes; class GlobalSettingsInterface { public: virtual void on_setting_changed(const QString &key, const QVariant &value) = 0; }; class GlobalSettings : public QSettings { Q_OBJECT public: static const QString Key_General_Theme; static const QString Key_General_Style; static const QString Key_View_ZoomToFitDuringAcq; static const QString Key_View_ZoomToFitAfterAcq; static const QString Key_View_TriggerIsZeroTime; static const QString Key_View_ColoredBG; static const QString Key_View_StickyScrolling; static const QString Key_View_ShowSamplingPoints; static const QString Key_View_FillSignalHighAreas; static const QString Key_View_FillSignalHighAreaColor; static const QString Key_View_ShowAnalogMinorGrid; static const QString Key_View_ConversionThresholdDispMode; static const QString Key_View_DefaultDivHeight; static const QString Key_View_DefaultLogicHeight; static const QString Key_View_ShowHoverMarker; static const QString Key_View_SnapDistance; static const QString Key_View_CursorFillColor; static const QString Key_Dec_InitialStateConfigurable; static const QString Key_Dec_ExportFormat; static const QString Key_Log_BufferSize; static const QString Key_Log_NotifyOfStacktrace; enum ConvThrDispMode { ConvThrDispMode_None = 0, ConvThrDispMode_Background, ConvThrDispMode_Dots }; public: GlobalSettings(); void save_internal_defaults(); void set_defaults_where_needed(); void set_bright_theme_default_colors(); void set_dark_theme_default_colors(); bool current_theme_is_dark(); void apply_theme(); static void add_change_handler(GlobalSettingsInterface *cb); static void remove_change_handler(GlobalSettingsInterface *cb); void setValue(const QString& key, const QVariant& value); /** * Begins the tracking of changes. All changes will * be recorded until stop_tracking() is called. * The change tracking is global and doesn't support nesting. */ void start_tracking(); /** * Ends the tracking of changes without any changes to the settings. */ void stop_tracking(); /** * Ends the tracking of changes, undoing the changes since the * change tracking began. */ void undo_tracked_changes(); static void store_gvariant(QSettings &settings, GVariant *v); static GVariant* restore_gvariant(QSettings &settings); static void store_variantbase(QSettings &settings, Glib::VariantBase v); static Glib::VariantBase restore_variantbase(QSettings &settings); private: static vector callbacks_; static bool tracking_; static map tracked_changes_; static QString default_style_; static QPalette default_palette_; bool is_dark_theme_; }; } // namespace pv #endif // PULSEVIEW_GLOBALSETTINGS_HPP pulseview-0.4.1/pv/globalsettings.cpp000600 001750 001750 00000025740 13365561737 017373 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2017 Soeren Apel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "globalsettings.hpp" #include #include #include #include #include #include #include #include #include using std::map; using std::pair; using std::string; using std::vector; namespace pv { const vector< pair > Themes { {"None" , ""}, {"QDarkStyleSheet", ":/themes/qdarkstyle/style.qss"}, {"DarkStyle", ":/themes/darkstyle/darkstyle.qss"} }; const QString GlobalSettings::Key_General_Theme = "General_Theme"; const QString GlobalSettings::Key_General_Style = "General_Style"; const QString GlobalSettings::Key_View_ZoomToFitDuringAcq = "View_ZoomToFitDuringAcq"; const QString GlobalSettings::Key_View_ZoomToFitAfterAcq = "View_ZoomToFitAfterAcq"; const QString GlobalSettings::Key_View_TriggerIsZeroTime = "View_TriggerIsZeroTime"; const QString GlobalSettings::Key_View_ColoredBG = "View_ColoredBG"; const QString GlobalSettings::Key_View_StickyScrolling = "View_StickyScrolling"; const QString GlobalSettings::Key_View_ShowSamplingPoints = "View_ShowSamplingPoints"; const QString GlobalSettings::Key_View_FillSignalHighAreas = "View_FillSignalHighAreas"; const QString GlobalSettings::Key_View_FillSignalHighAreaColor = "View_FillSignalHighAreaColor"; const QString GlobalSettings::Key_View_ShowAnalogMinorGrid = "View_ShowAnalogMinorGrid"; const QString GlobalSettings::Key_View_ConversionThresholdDispMode = "View_ConversionThresholdDispMode"; const QString GlobalSettings::Key_View_DefaultDivHeight = "View_DefaultDivHeight"; const QString GlobalSettings::Key_View_DefaultLogicHeight = "View_DefaultLogicHeight"; const QString GlobalSettings::Key_View_ShowHoverMarker = "View_ShowHoverMarker"; const QString GlobalSettings::Key_View_SnapDistance = "View_SnapDistance"; const QString GlobalSettings::Key_View_CursorFillColor = "View_CursorFillColor"; const QString GlobalSettings::Key_Dec_InitialStateConfigurable = "Dec_InitialStateConfigurable"; const QString GlobalSettings::Key_Dec_ExportFormat = "Dec_ExportFormat"; const QString GlobalSettings::Key_Log_BufferSize = "Log_BufferSize"; const QString GlobalSettings::Key_Log_NotifyOfStacktrace = "Log_NotifyOfStacktrace"; vector GlobalSettings::callbacks_; bool GlobalSettings::tracking_ = false; map GlobalSettings::tracked_changes_; QString GlobalSettings::default_style_; QPalette GlobalSettings::default_palette_; GlobalSettings::GlobalSettings() : QSettings(), is_dark_theme_(false) { beginGroup("Settings"); } void GlobalSettings::save_internal_defaults() { default_style_ = qApp->style()->objectName(); if (default_style_.isEmpty()) default_style_ = "fusion"; default_palette_ = QApplication::palette(); } void GlobalSettings::set_defaults_where_needed() { // Use no theme by default if (!contains(Key_General_Theme)) setValue(Key_General_Theme, 0); if (!contains(Key_General_Style)) setValue(Key_General_Style, ""); // Enable zoom-to-fit after acquisition by default if (!contains(Key_View_ZoomToFitAfterAcq)) setValue(Key_View_ZoomToFitAfterAcq, true); // Enable colored trace backgrounds by default if (!contains(Key_View_ColoredBG)) setValue(Key_View_ColoredBG, true); // Enable showing sampling points by default if (!contains(Key_View_ShowSamplingPoints)) setValue(Key_View_ShowSamplingPoints, true); // Enable filling logic signal high areas by default if (!contains(Key_View_FillSignalHighAreas)) setValue(Key_View_FillSignalHighAreas, true); if (!contains(Key_View_DefaultDivHeight)) setValue(Key_View_DefaultDivHeight, 3 * QFontMetrics(QApplication::font()).height()); if (!contains(Key_View_DefaultLogicHeight)) setValue(Key_View_DefaultLogicHeight, 2 * QFontMetrics(QApplication::font()).height()); if (!contains(Key_View_ShowHoverMarker)) setValue(Key_View_ShowHoverMarker, true); if (!contains(Key_View_SnapDistance)) setValue(Key_View_SnapDistance, 15); if (!contains(Key_Dec_ExportFormat)) setValue(Key_Dec_ExportFormat, "%s %d: %c: %1"); // Default to 500 lines of backlog if (!contains(Key_Log_BufferSize)) setValue(Key_Log_BufferSize, 500); // Notify user of existing stack trace by default if (!contains(Key_Log_NotifyOfStacktrace)) setValue(Key_Log_NotifyOfStacktrace, true); // Default theme is bright, so use its color scheme if undefined if (!contains(Key_View_CursorFillColor)) set_bright_theme_default_colors(); } void GlobalSettings::set_bright_theme_default_colors() { setValue(Key_View_FillSignalHighAreaColor, QColor(0, 0, 0, 5 * 256 / 100).rgba()); setValue(Key_View_CursorFillColor, QColor(220, 231, 243).rgba()); } void GlobalSettings::set_dark_theme_default_colors() { setValue(Key_View_FillSignalHighAreaColor, QColor(188, 188, 188, 9 * 256 / 100).rgba()); setValue(Key_View_CursorFillColor, QColor(60, 60, 60).rgba()); } bool GlobalSettings::current_theme_is_dark() { return is_dark_theme_; } void GlobalSettings::apply_theme() { QString theme_name = Themes.at(value(Key_General_Theme).toInt()).first; QString resource_name = Themes.at(value(Key_General_Theme).toInt()).second; if (!resource_name.isEmpty()) { QFile file(resource_name); file.open(QFile::ReadOnly | QFile::Text); qApp->setStyleSheet(file.readAll()); } else qApp->setStyleSheet(""); qApp->setPalette(default_palette_); const QString style = value(Key_General_Style).toString(); if (style.isEmpty()) qApp->setStyle(default_style_); else qApp->setStyle(style); is_dark_theme_ = false; if (theme_name.compare("QDarkStyleSheet") == 0) { QPalette dark_palette; dark_palette.setColor(QPalette::Window, QColor(53, 53, 53)); dark_palette.setColor(QPalette::WindowText, Qt::white); dark_palette.setColor(QPalette::Base, QColor(42, 42, 42)); dark_palette.setColor(QPalette::Dark, QColor(35, 35, 35)); dark_palette.setColor(QPalette::Highlight, QColor(42, 130, 218)); qApp->setPalette(dark_palette); is_dark_theme_ = true; } else if (theme_name.compare("DarkStyle") == 0) { QPalette dark_palette; dark_palette.setColor(QPalette::Window, QColor(53, 53, 53)); dark_palette.setColor(QPalette::WindowText, Qt::white); dark_palette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127)); dark_palette.setColor(QPalette::Base, QColor(42, 42, 42)); dark_palette.setColor(QPalette::AlternateBase, QColor(66, 66, 66)); dark_palette.setColor(QPalette::ToolTipBase, Qt::white); dark_palette.setColor(QPalette::ToolTipText, QColor(53, 53, 53)); dark_palette.setColor(QPalette::Text, Qt::white); dark_palette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127)); dark_palette.setColor(QPalette::Dark, QColor(35, 35, 35)); dark_palette.setColor(QPalette::Shadow, QColor(20, 20, 20)); dark_palette.setColor(QPalette::Button, QColor(53, 53, 53)); dark_palette.setColor(QPalette::ButtonText, Qt::white); dark_palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127)); dark_palette.setColor(QPalette::BrightText, Qt::red); dark_palette.setColor(QPalette::Link, QColor(42, 130, 218)); dark_palette.setColor(QPalette::Highlight, QColor(42, 130, 218)); dark_palette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80)); dark_palette.setColor(QPalette::HighlightedText, Qt::white); dark_palette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127)); qApp->setPalette(dark_palette); is_dark_theme_ = true; } QPixmapCache::clear(); } void GlobalSettings::add_change_handler(GlobalSettingsInterface *cb) { callbacks_.push_back(cb); } void GlobalSettings::remove_change_handler(GlobalSettingsInterface *cb) { for (auto cb_it = callbacks_.begin(); cb_it != callbacks_.end(); cb_it++) if (*cb_it == cb) { callbacks_.erase(cb_it); break; } } void GlobalSettings::setValue(const QString &key, const QVariant &value) { // Save previous value if we're tracking changes, // not altering an already-existing saved setting if (tracking_) tracked_changes_.emplace(key, QSettings::value(key)); QSettings::setValue(key, value); // TODO Emulate noquote() qDebug() << "Setting" << key << "changed to" << value; // Call all registered callbacks for (GlobalSettingsInterface *cb : callbacks_) cb->on_setting_changed(key, value); } void GlobalSettings::start_tracking() { tracking_ = true; tracked_changes_.clear(); } void GlobalSettings::stop_tracking() { tracking_ = false; tracked_changes_.clear(); } void GlobalSettings::undo_tracked_changes() { tracking_ = false; for (auto& entry : tracked_changes_) setValue(entry.first, entry.second); tracked_changes_.clear(); } void GlobalSettings::store_gvariant(QSettings &settings, GVariant *v) { const GVariantType *var_type = g_variant_get_type(v); char *var_type_str = g_variant_type_dup_string(var_type); QByteArray var_data = QByteArray((const char*)g_variant_get_data(v), g_variant_get_size(v)); settings.setValue("value", var_data); settings.setValue("type", var_type_str); g_free(var_type_str); } GVariant* GlobalSettings::restore_gvariant(QSettings &settings) { QString raw_type = settings.value("type").toString(); GVariantType *var_type = g_variant_type_new(raw_type.toUtf8()); QByteArray data = settings.value("value").toByteArray(); gpointer var_data = g_memdup((gconstpointer)data.constData(), (guint)data.size()); GVariant *value = g_variant_new_from_data(var_type, var_data, data.size(), false, g_free, var_data); g_variant_type_free(var_type); return value; } void GlobalSettings::store_variantbase(QSettings &settings, Glib::VariantBase v) { const QByteArray var_data = QByteArray((const char*)v.get_data(), v.get_size()); settings.setValue("value", var_data); settings.setValue("type", QString::fromStdString(v.get_type_string())); } Glib::VariantBase GlobalSettings::restore_variantbase(QSettings &settings) { QString raw_type = settings.value("type").toString(); GVariantType *var_type = g_variant_type_new(raw_type.toUtf8()); QByteArray data = settings.value("value").toByteArray(); gpointer var_data = g_memdup((gconstpointer)data.constData(), (guint)data.size()); GVariant *value = g_variant_new_from_data(var_type, var_data, data.size(), false, g_free, var_data); Glib::VariantBase ret_val = Glib::VariantBase(value, true); g_variant_type_free(var_type); g_variant_unref(value); return ret_val; } } // namespace pv pulseview-0.4.1/pv/storesession.hpp000600 001750 001750 00000005053 13365561737 017112 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2014 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_STORESESSION_HPP #define PULSEVIEW_PV_STORESESSION_HPP #include #include #include #include #include #include #include #include #include #include using std::atomic; using std::string; using std::shared_ptr; using std::pair; using std::map; using std::vector; using std::thread; using std::mutex; using std::ofstream; namespace sigrok { class Output; class OutputFormat; } namespace pv { class Session; namespace data { class SignalBase; class AnalogSegment; class LogicSegment; } class StoreSession : public QObject { Q_OBJECT private: static const size_t BlockSize; public: StoreSession(const string &file_name, const shared_ptr &output_format, const map &options, const pair sample_range, const Session &session); ~StoreSession(); pair progress() const; const QString& error() const; bool start(); void wait(); void cancel(); private: void store_proc(vector< shared_ptr > achannel_list, vector< shared_ptr > asegment_list, shared_ptr lsegment); Q_SIGNALS: void progress_updated(); void store_successful(); private: const string file_name_; const shared_ptr output_format_; const map options_; const pair sample_range_; const Session &session_; shared_ptr output_; ofstream output_stream_; std::thread thread_; atomic interrupt_; atomic units_stored_, unit_count_; mutable mutex mutex_; QString error_; uint64_t start_sample_, sample_count_; }; } // namespace pv #endif // PULSEVIEW_PV_STORESESSION_HPP pulseview-0.4.1/pv/devicemanager.hpp000600 001750 001750 00000004761 13365561737 017151 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_DEVICEMANAGER_HPP #define PULSEVIEW_PV_DEVICEMANAGER_HPP #include #include #include #include #include #include using std::list; using std::map; using std::set; using std::shared_ptr; using std::string; using std::vector; namespace Glib { class VariantBase; } namespace sigrok { class ConfigKey; class Context; class Driver; } using sigrok::ConfigKey; namespace pv { namespace devices { class Device; class HardwareDevice; } class Session; class DeviceManager { public: DeviceManager(shared_ptr context, std::string driver, bool do_scan); ~DeviceManager() = default; const shared_ptr& context() const; shared_ptr context(); const list< shared_ptr >& devices() const; shared_ptr user_spec_device() const; bool driver_supported(shared_ptr driver) const; list< shared_ptr > driver_scan( shared_ptr driver, map drvopts); const map get_device_info( const shared_ptr device); const shared_ptr find_device_from_info( const map search_info); private: bool compare_devices(shared_ptr a, shared_ptr b); static map drive_scan_options(vector user_spec, set driver_opts); protected: shared_ptr context_; list< shared_ptr > devices_; shared_ptr user_spec_device_; }; } // namespace pv #endif // PULSEVIEW_PV_DEVICEMANAGER_HPP pulseview-0.4.1/pv/prop/000700 001750 001750 00000000000 13365562011 014576 5ustar00uweuwe000000 000000 pulseview-0.4.1/pv/prop/enum.hpp000600 001750 001750 00000003306 13365561737 016275 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_PROP_ENUM_HPP #define PULSEVIEW_PV_PROP_ENUM_HPP #include #include #include "property.hpp" #include using std::pair; using std::vector; Q_DECLARE_METATYPE(Glib::VariantBase); class QComboBox; class QLabel; class QSlider; namespace pv { namespace prop { class Enum : public Property { Q_OBJECT; public: Enum(QString name, QString desc, vector > values, Getter getter, Setter setter); virtual ~Enum() = default; QWidget* get_widget(QWidget *parent, bool auto_commit); void update_widget(); void commit(); private Q_SLOTS: void on_current_index_changed(int); void on_value_changed(int); private: const vector< pair > values_; bool is_range_; QComboBox *selector_; QWidget *slider_layout_widget_; QSlider *slider_; QLabel *slider_label_; }; } // namespace prop } // namespace pv #endif // PULSEVIEW_PV_PROP_ENUM_HPP pulseview-0.4.1/pv/prop/string.cpp000600 001750 001750 00000004553 13365561737 016637 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include "string.hpp" using std::string; using Glib::ustring; namespace pv { namespace prop { String::String(QString name, QString desc, Getter getter, Setter setter) : Property(name, desc, getter, setter), line_edit_(nullptr) { } QWidget* String::get_widget(QWidget *parent, bool auto_commit) { if (line_edit_) return line_edit_; if (!getter_) return nullptr; try { Glib::VariantBase variant = getter_(); if (!variant.gobj()) return nullptr; } catch (const sigrok::Error &e) { qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what()); return nullptr; } line_edit_ = new QLineEdit(parent); update_widget(); if (auto_commit) connect(line_edit_, SIGNAL(textEdited(const QString&)), this, SLOT(on_text_edited(const QString&))); return line_edit_; } void String::update_widget() { if (!line_edit_) return; Glib::VariantBase variant; try { variant = getter_(); } catch (const sigrok::Error &e) { qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what()); return; } assert(variant.gobj()); string value = Glib::VariantBase::cast_dynamic>( variant).get(); line_edit_->setText(QString::fromStdString(value)); } void String::commit() { assert(setter_); if (!line_edit_) return; QByteArray ba = line_edit_->text().toLocal8Bit(); setter_(Glib::Variant::create(ba.data())); } void String::on_text_edited(const QString&) { commit(); } } // namespace prop } // namespace pv pulseview-0.4.1/pv/prop/int.cpp000600 001750 001750 00000013000 13365561737 016106 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include "int.hpp" using boost::optional; using std::max; using std::min; using std::pair; namespace pv { namespace prop { Int::Int(QString name, QString desc, QString suffix, optional< pair > range, Getter getter, Setter setter) : Property(name, desc, getter, setter), suffix_(suffix), range_(range), spin_box_(nullptr) { } QWidget* Int::get_widget(QWidget *parent, bool auto_commit) { int64_t range_min = 0; uint64_t range_max = 0; if (spin_box_) return spin_box_; if (!getter_) return nullptr; try { value_ = getter_(); } catch (const sigrok::Error &e) { qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what()); return nullptr; } GVariant *value = value_.gobj(); if (!value) return nullptr; spin_box_ = new QSpinBox(parent); spin_box_->setSuffix(suffix_); const GVariantType *const type = g_variant_get_type(value); assert(type); if (g_variant_type_equal(type, G_VARIANT_TYPE_BYTE)) { range_min = 0, range_max = UINT8_MAX; } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT16)) { range_min = INT16_MIN, range_max = INT16_MAX; } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT16)) { range_min = 0, range_max = UINT16_MAX; } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT32)) { range_min = INT32_MIN, range_max = INT32_MAX; } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT32)) { range_min = 0, range_max = UINT32_MAX; } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT64)) { range_min = INT64_MIN, range_max = INT64_MAX; } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT64)) { range_min = 0, range_max = UINT64_MAX; } else { // Unexpected value type. assert(false); } // @todo sigrok supports 64-bit quantities, but Qt does not have a // standard widget to allow the values to be modified over the full // 64-bit range on 32-bit machines. To solve the issue we need a // custom widget. range_min = max(range_min, (int64_t)INT_MIN); range_max = min(range_max, (uint64_t)INT_MAX); if (range_) spin_box_->setRange((int)range_->first, (int)range_->second); else spin_box_->setRange((int)range_min, (int)range_max); update_widget(); if (auto_commit) connect(spin_box_, SIGNAL(valueChanged(int)), this, SLOT(on_value_changed(int))); return spin_box_; } void Int::update_widget() { if (!spin_box_) return; try { value_ = getter_(); } catch (const sigrok::Error &e) { qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what()); return; } GVariant *value = value_.gobj(); assert(value); const GVariantType *const type = g_variant_get_type(value); assert(type); int64_t int_val = 0; if (g_variant_type_equal(type, G_VARIANT_TYPE_BYTE)) { int_val = g_variant_get_byte(value); } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT16)) { int_val = g_variant_get_int16(value); } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT16)) { int_val = g_variant_get_uint16(value); } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT32)) { int_val = g_variant_get_int32(value); } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT32)) { int_val = g_variant_get_uint32(value); } else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT64)) { int_val = g_variant_get_int64(value); } else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT64)) { int_val = g_variant_get_uint64(value); } else { // Unexpected value type. assert(false); } spin_box_->setValue((int)int_val); } void Int::commit() { assert(setter_); if (!spin_box_) return; GVariant *new_value = nullptr; const GVariantType *const type = g_variant_get_type(value_.gobj()); assert(type); if (g_variant_type_equal(type, G_VARIANT_TYPE_BYTE)) new_value = g_variant_new_byte(spin_box_->value()); else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT16)) new_value = g_variant_new_int16(spin_box_->value()); else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT16)) new_value = g_variant_new_uint16(spin_box_->value()); else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT32)) new_value = g_variant_new_int32(spin_box_->value()); else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT32)) new_value = g_variant_new_uint32(spin_box_->value()); else if (g_variant_type_equal(type, G_VARIANT_TYPE_INT64)) new_value = g_variant_new_int64(spin_box_->value()); else if (g_variant_type_equal(type, G_VARIANT_TYPE_UINT64)) new_value = g_variant_new_uint64(spin_box_->value()); else { // Unexpected value type. assert(false); } assert(new_value); value_ = Glib::VariantBase(new_value); setter_(value_); } void Int::on_value_changed(int) { commit(); } } // namespace prop } // namespace pv pulseview-0.4.1/pv/prop/bool.hpp000600 001750 001750 00000002512 13365561737 016262 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_PROP_BOOL_HPP #define PULSEVIEW_PV_PROP_BOOL_HPP #include "property.hpp" class QCheckBox; namespace pv { namespace prop { class Bool : public Property { Q_OBJECT; public: Bool(QString name, QString desc, Getter getter, Setter setter); virtual ~Bool() = default; QWidget* get_widget(QWidget *parent, bool auto_commit); bool labeled_widget() const; void update_widget(); void commit(); private Q_SLOTS: void on_state_changed(int); private: QCheckBox *check_box_; }; } // namespace prop } // namespace pv #endif // PULSEVIEW_PV_PROP_BOOL_HPP pulseview-0.4.1/pv/prop/double.hpp000600 001750 001750 00000003207 13365561737 016603 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_PROP_DOUBLE_HPP #define PULSEVIEW_PV_PROP_DOUBLE_HPP #include #include #include "property.hpp" using std::pair; class QDoubleSpinBox; namespace pv { namespace prop { class Double : public Property { Q_OBJECT public: Double(QString name, QString desc, int decimals, QString suffix, boost::optional< pair > range, boost::optional step, Getter getter, Setter setter); virtual ~Double() = default; QWidget* get_widget(QWidget *parent, bool auto_commit); void update_widget(); void commit(); private Q_SLOTS: void on_value_changed(double); private: const int decimals_; const QString suffix_; const boost::optional< pair > range_; const boost::optional step_; QDoubleSpinBox *spin_box_; }; } // namespace prop } // namespace pv #endif // PULSEVIEW_PV_PROP_DOUBLE_HPP pulseview-0.4.1/pv/prop/enum.cpp000600 001750 001750 00000016362 13365561737 016276 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "enum.hpp" using std::abs; // Note that "using std::isnan;" is _not_ put here since that would break // compilation on some platforms. Use "std::isnan()" instead in checks below. using std::numeric_limits; using std::pair; using std::vector; namespace pv { namespace prop { Enum::Enum(QString name, QString desc, vector > values, Getter getter, Setter setter) : Property(name, desc, getter, setter), values_(values), is_range_(false), selector_(nullptr), slider_layout_widget_(nullptr), slider_(nullptr), slider_label_(nullptr) { // Try to determine whether the values make up a range, created by e.g. // std_gvar_min_max_step_thresholds() vector deltas; double prev_value = 0; for (const pair &v : values_) { gdouble value; if (v.first.is_of_type(Glib::VariantType("d"))) { g_variant_get((GVariant*)(v.first.gobj()), "d", &value); } else if (v.first.is_of_type(Glib::VariantType("(dd)"))) { gdouble dummy; g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &value, &dummy); } else break; // Type not d or (dd), so not a range that we can handle deltas.push_back(value - prev_value); prev_value = value; } if (deltas.size() > 0) { bool constant_delta = true; double prev_delta = numeric_limits::quiet_NaN(); bool skip_first = true; for (double delta : deltas) { // First value is incorrect, it's the delta to 0 since no // previous value existed yet if (skip_first) { skip_first = false; continue; } if (std::isnan(prev_delta)) prev_delta = delta; // 2*DBL_EPSILON doesn't work here, so use a workaround if (abs(delta - prev_delta) > (delta/10)) constant_delta = false; prev_delta = delta; } if (constant_delta) is_range_ = true; } } QWidget* Enum::get_widget(QWidget *parent, bool auto_commit) { if (!getter_) return nullptr; Glib::VariantBase variant; try { variant = getter_(); } catch (const sigrok::Error &e) { qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what()); return nullptr; } if (!variant.gobj()) return nullptr; if (is_range_) { // Use slider if (slider_layout_widget_) return slider_layout_widget_; slider_ = new QSlider(); // Sliders can't handle float values, so we just use it to specify // the number of steps that we're away from the range's beginning slider_->setOrientation(Qt::Horizontal); slider_->setMinimum(0); slider_->setMaximum(values_.size() - 1); slider_->setSingleStep(1); slider_label_ = new QLabel(); slider_layout_widget_ = new QWidget(parent); QHBoxLayout *layout = new QHBoxLayout(slider_layout_widget_); layout->addWidget(slider_); layout->addWidget(slider_label_); update_widget(); if (auto_commit) connect(slider_, SIGNAL(valueChanged(int)), this, SLOT(on_value_changed(int))); return slider_layout_widget_; } else { // Use combo box if (selector_) return selector_; selector_ = new QComboBox(parent); for (unsigned int i = 0; i < values_.size(); i++) { const pair &v = values_[i]; selector_->addItem(v.second, qVariantFromValue(v.first)); } update_widget(); if (auto_commit) connect(selector_, SIGNAL(currentIndexChanged(int)), this, SLOT(on_current_index_changed(int))); return selector_; } } void Enum::update_widget() { Glib::VariantBase variant; try { variant = getter_(); } catch (const sigrok::Error &e) { qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what()); return; } assert(variant.gobj()); if (is_range_) { // Use slider if (!slider_layout_widget_) return; for (unsigned int i = 0; i < values_.size(); i++) { const pair &v = values_[i]; // g_variant_equal() doesn't handle floating point properly if (v.first.is_of_type(Glib::VariantType("d"))) { gdouble a, b; g_variant_get(variant.gobj(), "d", &a); g_variant_get((GVariant*)(v.first.gobj()), "d", &b); if (abs(a - b) <= 2 * DBL_EPSILON) { slider_->setValue(i); slider_label_->setText(v.second); } } else { // Check for "(dd)" type and handle it if it's found if (v.first.is_of_type(Glib::VariantType("(dd)"))) { gdouble a1, a2, b1, b2; g_variant_get(variant.gobj(), "(dd)", &a1, &a2); g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &b1, &b2); if ((abs(a1 - b1) <= 2 * DBL_EPSILON) && \ (abs(a2 - b2) <= 2 * DBL_EPSILON)) { slider_->setValue(i); slider_label_->setText(v.second); } } else { qWarning() << "Enum property" << name() << "encountered unsupported type"; return; } } } } else { // Use combo box if (!selector_) return; for (unsigned int i = 0; i < values_.size(); i++) { const pair &v = values_[i]; // g_variant_equal() doesn't handle floating point properly if (v.first.is_of_type(Glib::VariantType("d"))) { gdouble a, b; g_variant_get(variant.gobj(), "d", &a); g_variant_get((GVariant*)(v.first.gobj()), "d", &b); if (abs(a - b) <= 2 * DBL_EPSILON) selector_->setCurrentIndex(i); } else { // Check for "(dd)" type and handle it if it's found if (v.first.is_of_type(Glib::VariantType("(dd)"))) { gdouble a1, a2, b1, b2; g_variant_get(variant.gobj(), "(dd)", &a1, &a2); g_variant_get((GVariant*)(v.first.gobj()), "(dd)", &b1, &b2); if ((abs(a1 - b1) <= 2 * DBL_EPSILON) && \ (abs(a2 - b2) <= 2 * DBL_EPSILON)) selector_->setCurrentIndex(i); } else // Handle all other types if (v.first.equal(variant)) selector_->setCurrentIndex(i); } } } } void Enum::commit() { assert(setter_); if (is_range_) { // Use slider if (!slider_layout_widget_) return; setter_(values_.at(slider_->value()).first); update_widget(); } else { // Use combo box if (!selector_) return; const int index = selector_->currentIndex(); if (index < 0) return; setter_(selector_->itemData(index).value()); // The combo box needs no update, it already shows the current value // by definition: the user picked it } } void Enum::on_current_index_changed(int) { commit(); } void Enum::on_value_changed(int) { commit(); } } // namespace prop } // namespace pv pulseview-0.4.1/pv/prop/property.cpp000600 001750 001750 00000002246 13365561737 017212 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "property.hpp" namespace pv { namespace prop { Property::Property(QString name, QString desc, Getter getter, Setter setter) : getter_(getter), setter_(setter), name_(name), desc_(desc) { } const QString& Property::name() const { return name_; } const QString& Property::desc() const { return desc_; } bool Property::labeled_widget() const { return false; } } // namespace prop } // namespace pv pulseview-0.4.1/pv/prop/int.hpp000600 001750 001750 00000003032 13365561737 016117 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_PROP_INT_HPP #define PULSEVIEW_PV_PROP_INT_HPP #include #include #include "property.hpp" using std::pair; class QSpinBox; namespace pv { namespace prop { class Int : public Property { Q_OBJECT; public: Int(QString name, QString desc, QString suffix, boost::optional< pair > range, Getter getter, Setter setter); virtual ~Int() = default; QWidget* get_widget(QWidget *parent, bool auto_commit); void update_widget(); void commit(); private Q_SLOTS: void on_value_changed(int); private: const QString suffix_; const boost::optional< pair > range_; Glib::VariantBase value_; QSpinBox *spin_box_; }; } // namespace prop } // namespace pv #endif // PULSEVIEW_PV_PROP_INT_HPP pulseview-0.4.1/pv/prop/bool.cpp000600 001750 001750 00000004515 13365561737 016262 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include "bool.hpp" namespace pv { namespace prop { Bool::Bool(QString name, QString desc, Getter getter, Setter setter) : Property(name, desc, getter, setter), check_box_(nullptr) { } QWidget* Bool::get_widget(QWidget *parent, bool auto_commit) { if (check_box_) return check_box_; if (!getter_) return nullptr; try { Glib::VariantBase variant = getter_(); if (!variant.gobj()) return nullptr; } catch (const sigrok::Error &e) { qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what()); return nullptr; } check_box_ = new QCheckBox(name(), parent); check_box_->setToolTip(desc()); update_widget(); if (auto_commit) connect(check_box_, SIGNAL(stateChanged(int)), this, SLOT(on_state_changed(int))); return check_box_; } bool Bool::labeled_widget() const { return true; } void Bool::update_widget() { if (!check_box_) return; Glib::VariantBase variant; try { variant = getter_(); } catch (const sigrok::Error &e) { qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what()); return; } assert(variant.gobj()); bool value = Glib::VariantBase::cast_dynamic>( variant).get(); check_box_->setCheckState(value ? Qt::Checked : Qt::Unchecked); } void Bool::commit() { assert(setter_); if (!check_box_) return; setter_(Glib::Variant::create(check_box_->checkState() == Qt::Checked)); } void Bool::on_state_changed(int) { commit(); } } // namespace prop } // namespace pv pulseview-0.4.1/pv/prop/property.hpp000600 001750 001750 00000003447 13365561737 017223 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_PROP_PROPERTY_HPP #define PULSEVIEW_PV_PROP_PROPERTY_HPP #include // Suppress warnings due to use of deprecated std::auto_ptr<> by glibmm. G_GNUC_BEGIN_IGNORE_DEPRECATIONS #include G_GNUC_END_IGNORE_DEPRECATIONS #include #include #include using std::function; class QWidget; namespace pv { namespace prop { class Property : public QObject { Q_OBJECT; public: typedef function Getter; typedef function Setter; protected: Property(QString name, QString desc, Getter getter, Setter setter); public: const QString& name() const; const QString& desc() const; virtual QWidget* get_widget(QWidget *parent, bool auto_commit = false) = 0; virtual bool labeled_widget() const; virtual void update_widget() = 0; virtual void commit() = 0; protected: const Getter getter_; const Setter setter_; protected: QString name_; QString desc_; }; } // namespace prop } // namespace pv #endif // PULSEVIEW_PV_PROP_PROPERTY_HPP pulseview-0.4.1/pv/prop/string.hpp000600 001750 001750 00000002442 13365561737 016637 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef PULSEVIEW_PV_PROP_STRING_HPP #define PULSEVIEW_PV_PROP_STRING_HPP #include "property.hpp" class QLineEdit; namespace pv { namespace prop { class String : public Property { Q_OBJECT; public: String(QString name, QString desc, Getter getter, Setter setter); QWidget* get_widget(QWidget *parent, bool auto_commit); void update_widget(); void commit(); private Q_SLOTS: void on_text_edited(const QString&); private: QLineEdit *line_edit_; }; } // namespace prop } // namespace pv #endif // PULSEVIEW_PV_PROP_STRING_HPP pulseview-0.4.1/pv/prop/double.cpp000600 001750 001750 00000005137 13365561737 016602 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2013 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include "double.hpp" using boost::optional; using std::pair; namespace pv { namespace prop { Double::Double(QString name, QString desc, int decimals, QString suffix, optional< pair > range, optional step, Getter getter, Setter setter) : Property(name, desc, getter, setter), decimals_(decimals), suffix_(suffix), range_(range), step_(step), spin_box_(nullptr) { } QWidget* Double::get_widget(QWidget *parent, bool auto_commit) { if (spin_box_) return spin_box_; if (!getter_) return nullptr; try { Glib::VariantBase variant = getter_(); if (!variant.gobj()) return nullptr; } catch (const sigrok::Error &e) { qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what()); return nullptr; } spin_box_ = new QDoubleSpinBox(parent); spin_box_->setDecimals(decimals_); spin_box_->setSuffix(suffix_); if (range_) spin_box_->setRange(range_->first, range_->second); if (step_) spin_box_->setSingleStep(*step_); update_widget(); if (auto_commit) connect(spin_box_, SIGNAL(valueChanged(double)), this, SLOT(on_value_changed(double))); return spin_box_; } void Double::update_widget() { if (!spin_box_) return; Glib::VariantBase variant; try { variant = getter_(); } catch (const sigrok::Error &e) { qWarning() << tr("Querying config key %1 resulted in %2").arg(name_, e.what()); return; } assert(variant.gobj()); double value = Glib::VariantBase::cast_dynamic>( variant).get(); spin_box_->setValue(value); } void Double::commit() { assert(setter_); if (!spin_box_) return; setter_(Glib::Variant::create(spin_box_->value())); } void Double::on_value_changed(double) { commit(); } } // namespace prop } // namespace pv pulseview-0.4.1/pv/session.cpp000600 001750 001750 00000100553 13365561737 016031 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2012-14 Joel Holdsworth * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include "devicemanager.hpp" #include "mainwindow.hpp" #include "session.hpp" #include "data/analog.hpp" #include "data/analogsegment.hpp" #include "data/decode/decoder.hpp" #include "data/logic.hpp" #include "data/logicsegment.hpp" #include "data/signalbase.hpp" #include "devices/hardwaredevice.hpp" #include "devices/inputfile.hpp" #include "devices/sessionfile.hpp" #include "toolbars/mainbar.hpp" #include "views/trace/analogsignal.hpp" #include "views/trace/decodetrace.hpp" #include "views/trace/logicsignal.hpp" #include "views/trace/signal.hpp" #include "views/trace/view.hpp" #include #ifdef ENABLE_DECODE #include #include "data/decodesignal.hpp" #endif using std::bad_alloc; using std::dynamic_pointer_cast; using std::find_if; using std::function; using std::lock_guard; using std::list; using std::make_pair; using std::make_shared; using std::map; using std::max; using std::move; using std::mutex; using std::pair; using std::recursive_mutex; using std::runtime_error; using std::shared_ptr; using std::string; using std::unique_ptr; using std::unordered_set; using std::vector; using sigrok::Analog; using sigrok::Channel; using sigrok::ConfigKey; using sigrok::DatafeedCallbackFunction; using sigrok::Error; using sigrok::InputFormat; using sigrok::Logic; using sigrok::Meta; using sigrok::Packet; using sigrok::Session; using Glib::VariantBase; namespace pv { shared_ptr Session::sr_context; Session::Session(DeviceManager &device_manager, QString name) : device_manager_(device_manager), default_name_(name), name_(name), capture_state_(Stopped), cur_samplerate_(0), data_saved_(true) { } Session::~Session() { // Stop and join to the thread stop_capture(); } DeviceManager& Session::device_manager() { return device_manager_; } const DeviceManager& Session::device_manager() const { return device_manager_; } shared_ptr Session::session() const { if (!device_) return shared_ptr(); return device_->session(); } shared_ptr Session::device() const { return device_; } QString Session::name() const { return name_; } void Session::set_name(QString name) { if (default_name_.isEmpty()) default_name_ = name; name_ = name; name_changed(); } const list< shared_ptr > Session::views() const { return views_; } shared_ptr Session::main_view() const { return main_view_; } void Session::set_main_bar(shared_ptr main_bar) { main_bar_ = main_bar; } shared_ptr Session::main_bar() const { return main_bar_; } bool Session::data_saved() const { return data_saved_; } void Session::save_settings(QSettings &settings) const { map dev_info; list key_list; int decode_signals = 0, views = 0; if (device_) { shared_ptr hw_device = dynamic_pointer_cast< devices::HardwareDevice >(device_); if (hw_device) { settings.setValue("device_type", "hardware"); settings.beginGroup("device"); key_list.emplace_back("vendor"); key_list.emplace_back("model"); key_list.emplace_back("version"); key_list.emplace_back("serial_num"); key_list.emplace_back("connection_id"); dev_info = device_manager_.get_device_info(device_); for (string& key : key_list) { if (dev_info.count(key)) settings.setValue(QString::fromUtf8(key.c_str()), QString::fromUtf8(dev_info.at(key).c_str())); else settings.remove(QString::fromUtf8(key.c_str())); } settings.endGroup(); } shared_ptr sessionfile_device = dynamic_pointer_cast(device_); if (sessionfile_device) { settings.setValue("device_type", "sessionfile"); settings.beginGroup("device"); settings.setValue("filename", QString::fromStdString( sessionfile_device->full_name())); settings.endGroup(); } shared_ptr inputfile_device = dynamic_pointer_cast(device_); if (inputfile_device) { settings.setValue("device_type", "inputfile"); settings.beginGroup("device"); inputfile_device->save_meta_to_settings(settings); settings.endGroup(); } // Save channels and decoders for (const shared_ptr& base : signalbases_) { #ifdef ENABLE_DECODE if (base->is_decode_signal()) { settings.beginGroup("decode_signal" + QString::number(decode_signals++)); base->save_settings(settings); settings.endGroup(); } else #endif { settings.beginGroup(base->internal_name()); base->save_settings(settings); settings.endGroup(); } } settings.setValue("decode_signals", decode_signals); // Save view states and their signal settings // Note: main_view must be saved as view0 settings.beginGroup("view" + QString::number(views++)); main_view_->save_settings(settings); settings.endGroup(); for (const shared_ptr& view : views_) { if (view != main_view_) { settings.beginGroup("view" + QString::number(views++)); view->save_settings(settings); settings.endGroup(); } } settings.setValue("views", views); } } void Session::restore_settings(QSettings &settings) { shared_ptr device; QString device_type = settings.value("device_type").toString(); if (device_type == "hardware") { map dev_info; list key_list; // Re-select last used device if possible but only if it's not demo settings.beginGroup("device"); key_list.emplace_back("vendor"); key_list.emplace_back("model"); key_list.emplace_back("version"); key_list.emplace_back("serial_num"); key_list.emplace_back("connection_id"); for (string key : key_list) { const QString k = QString::fromStdString(key); if (!settings.contains(k)) continue; const string value = settings.value(k).toString().toStdString(); if (!value.empty()) dev_info.insert(make_pair(key, value)); } if (dev_info.count("model") > 0) device = device_manager_.find_device_from_info(dev_info); if (device) set_device(device); settings.endGroup(); } if ((device_type == "sessionfile") || (device_type == "inputfile")) { if (device_type == "sessionfile") { settings.beginGroup("device"); QString filename = settings.value("filename").toString(); settings.endGroup(); if (QFileInfo(filename).isReadable()) { device = make_shared(device_manager_.context(), filename.toStdString()); } } if (device_type == "inputfile") { settings.beginGroup("device"); device = make_shared(device_manager_.context(), settings); settings.endGroup(); } if (device) { set_device(device); start_capture([](QString infoMessage) { // TODO Emulate noquote() qDebug() << "Session error:" << infoMessage; }); set_name(QString::fromStdString( dynamic_pointer_cast(device)->display_name(device_manager_))); } } if (device) { // Restore channels for (shared_ptr base : signalbases_) { settings.beginGroup(base->internal_name()); base->restore_settings(settings); settings.endGroup(); } // Restore decoders #ifdef ENABLE_DECODE int decode_signals = settings.value("decode_signals").toInt(); for (int i = 0; i < decode_signals; i++) { settings.beginGroup("decode_signal" + QString::number(i)); shared_ptr signal = add_decode_signal(); signal->restore_settings(settings); settings.endGroup(); } #endif // Restore views int views = settings.value("views").toInt(); for (int i = 0; i < views; i++) { settings.beginGroup("view" + QString::number(i)); if (i > 0) { views::ViewType type = (views::ViewType)settings.value("type").toInt(); add_view(name_, type, this); views_.back()->restore_settings(settings); } else main_view_->restore_settings(settings); settings.endGroup(); } } } void Session::select_device(shared_ptr device) { try { if (device) set_device(device); else set_default_device(); } catch (const QString &e) { MainWindow::show_session_error(tr("Failed to select device"), e); } } void Session::set_device(shared_ptr device) { assert(device); // Ensure we are not capturing before setting the device stop_capture(); if (device_) device_->close(); device_.reset(); // Revert name back to default name (e.g. "Session 1") as the data is gone name_ = default_name_; name_changed(); // Remove all stored data and reset all views for (shared_ptr view : views_) { view->clear_signals(); #ifdef ENABLE_DECODE view->clear_decode_signals(); #endif view->reset_view_state(); } for (const shared_ptr& d : all_signal_data_) d->clear(); all_signal_data_.clear(); signalbases_.clear(); cur_logic_segment_.reset(); for (auto& entry : cur_analog_segments_) { shared_ptr(entry.first).reset(); shared_ptr(entry.second).reset(); } logic_data_.reset(); signals_changed(); device_ = move(device); try { device_->open(); } catch (const QString &e) { device_.reset(); MainWindow::show_session_error(tr("Failed to open device"), e); } if (device_) { device_->session()->add_datafeed_callback([=] (shared_ptr device, shared_ptr packet) { data_feed_in(device, packet); }); update_signals(); } device_changed(); } void Session::set_default_device() { const list< shared_ptr > &devices = device_manager_.devices(); if (devices.empty()) return; // Try and find the demo device and select that by default const auto iter = find_if(devices.begin(), devices.end(), [] (const shared_ptr &d) { return d->hardware_device()->driver()->name() == "demo"; }); set_device((iter == devices.end()) ? devices.front() : *iter); } /** * Convert generic options to data types that are specific to InputFormat. * * @param[in] user_spec Vector of tokenized words, string format. * @param[in] fmt_opts Input format's options, result of InputFormat::options(). * * @return Map of options suitable for InputFormat::create_input(). */ map Session::input_format_options(vector user_spec, map> fmt_opts) { map result; for (auto& entry : user_spec) { /* * Split key=value specs. Accept entries without separator * (for simplified boolean specifications). */ string key, val; size_t pos = entry.find("="); if (pos == std::string::npos) { key = entry; val = ""; } else { key = entry.substr(0, pos); val = entry.substr(pos + 1); } /* * Skip user specifications that are not a member of the * format's set of supported options. Have the text input * spec converted to the required input format specific * data type. */ auto found = fmt_opts.find(key); if (found == fmt_opts.end()) continue; shared_ptr