pulseview-0.4.2/l10n/000700 001750 001750 00000000000 13640725776 013763 5ustar00uweuwe000000 000000 pulseview-0.4.2/l10n/de.ts000600 001750 001750 00000201742 13640725356 014725 0ustar00uweuwe000000 000000 Application Some parts of the application may still use the previous language. Re-opening the affected windows or restarting the application will remedy this. Einige Teile der Anwendung verwenden vielleicht noch die vorherige Sprache. Sollte das der Fall sein, kann dies durch ein Schließen und neu Öffnen der betroffenen Fenster oder der Anwendung behoben werden. QApplication Select a decoder to see its description here. Wähle einen Dekoder, um dessen Beschreibung hier lesen zu können. Session %1 Analysesitzung %1 Querying config key %1 is not allowed Internal message Querying config key %1 resulted in %2 Internal message Unknown type supplied when attempting to query %1 Internal message Error when scanning device driver '%1': %2 Internal message QHexView No data available Keine Daten vorhanden QObject Cancel Abbrechen Scanning for devices that driver %1 can access... Suche nach Geräten, die von Treiber %1 angesprochen werden können... Stack trace of previous crash: Internal message Don't show this message again Diese Meldung in Zukunft nicht mehr anzeigen When %1 last crashed, it created a stack trace. A human-readable form has been saved to disk and was written to the log. You may access it from the settings dialog. Internal message SubWindow <p align='right'>Tags: %1</p> <p align='right'>Stichworte: %1</p> pv::MainWindow PulseView Name Decoder Selector Protokolldekoder Session %1 Analysesitzung %1 Create New Session Neue Analysesitzung Start/Stop Acquisition Datenerfassung starten/stoppen Settings Einstellungen Reload Neu laden Confirmation Bestätigung There is unsaved data. Close anyway? Es gibt noch ungespeicherte Daten. Trotzdem beenden? Run Starten Stop Stoppen This session contains unsaved data. Close it anyway? Die Daten dieser Analysesitzung wurden nicht gespeichert. Trotzdem schließen? pv::Session Failed to select device Fehler beim Auswählen des Gerätes Failed to open device Fehler beim Öffnen des Gerätes Error Fehler Unexpected input format: %s Unerwartetes Importformat: %s Failed to load %1 Fehler beim Laden von %1 No active device set, can't start acquisition. Kein Gerät aktiv, kann Datenerfassung nicht starten. No channels enabled. Keine aktiven Kanäle vorhanden. Out of memory, acquisition stopped. Nicht genügend Arbeitsspeicher vorhanden, Datenerfassung wurde gestoppt. Can't handle more than 64 logic channels. Internal message pv::StoreSession Can't save logic channel without data. Kann Logikkanal nicht speichern, da er keine Daten beinhaltet. Can't save analog channel without data. Kann Analogkanal nicht speichern, da er keine Daten beinhaltet. No channels enabled. Keine Kanäle aktiviert. Can't save range without sample data. In dem gewählten Bereich befinden sich keine Daten zum Speichern. Error while saving: Fehler beim Speichern: pv::data::DecodeSignal No decoders Keine Protokolldekoder There are no channels assigned to this decoder Dem Protokolldekoder sind keine Kanäle zugeordnet One or more required channels have not been specified Mindestens ein notwendiger Kanal wurde noch nicht zugeordnet No input data Keine Daten zum Auswerten vorhanden Decoder reported an error Protokolldekoder meldet Fehler Failed to create decoder instance Fehler beim Erzeugen des Protokolldekoders pv::data::SignalBase Signal average Durchschnittlicher Signalpegel 0.9V (for 1.8V CMOS) 0.9V (für 1.8V CMOS) 1.8V (for 3.3V CMOS) 1.8V (für 3.3V CMOS) 2.5V (for 5.0V CMOS) 2.5V (für 5.0V CMOS) 1.5V (for TTL) 1.5V (für TTL) Signal average +/- 15% Durchschnittlicher Signalpegel +/- 15% 0.3V/1.2V (for 1.8V CMOS) 0.3V/1.2V (für 1.8V CMOS) 0.7V/2.5V (for 3.3V CMOS) 0.7V/2.5V (für 3.3V CMOS) 1.3V/3.7V (for 5.0V CMOS) 1.3V/3.7V (für 5.0V CMOS) 0.8V/2.0V (for TTL) 0.8V/2.0V (für TTL) pv::dialogs::Connect &Scan for devices using driver above Nach Geräten &suchen, die der ausgewählte Treiber ansprechen kann Connect to Device Mit Gerät verbinden Step 1: Choose the driver Schritt 1: Treiber auswählen &USB Serial &Port Serielle Sch&nittstelle &TCP/IP Protocol: Protokoll: Step 2: Choose the interface Schritt 2: Schnittstelle auswählen Step 3: Scan for devices Schritt 3: Nach Geräten suchen Step 4: Select the device Schritt 4: Gerät auswählen pv::dialogs::Settings General Allgemein Views Ansichten Decoders Protokolldekoder About Programmdetails Logging Programminterne Meldungen User interface language Sprache der Benutzeroberfläche User interface theme Design der Benutzeroberfläche (You may need to restart PulseView for all UI elements to update) (Ein Neustart von PulseView kann notwendig sein, damit alle Bedienelemente das neue Design übernehmen) System Default Standard Qt widget style Qt-Anzeigestil (Dark themes look best with the Fusion style) (Dunkle Designs sehen mit dem Fusion-Stil am besten aus) Save session &setup along with .sr file Analyse&sitzungs-Konfiguration zusammen mit .sr-Dateien speichern Trace View Signalansicht Use colored trace &background Verwende &farbigen Kanalhintergrund Constantly perform &zoom-to-fit during acquisition Ständig den &Zoom anpassen, während Daten aufgezeichnet werden Perform a zoom-to-&fit when acquisition stops Den Zoom &anpassen, wenn die Datenerfassung stoppt Show time zero at the trigger Den Triggerzeitpunkt automatisch als Nullpunkt festlegen Always keep &newest samples at the right edge during capture Die neuesten Datenpunkte während der Aufzeichnung immer am rechten &Rand anzeigen Show data &sampling points Daten&punkte visuell hervorheben Fill high areas of logic signals High-Pegel von Logiksignalen hervorheben Color to fill high areas of logic signals with Farbe für hervorgehobene High-Pegel Show analog minor grid in addition to div grid Vertikale Unterteilungen nochmals unterteilen Highlight mouse cursor using a vertical marker line Position des Mauscursors durch vertikalen Balken hervorheben pixels Pixel Maximum distance from edges before markers snap to them Abstand zu Signalflanken, bevor Markierer einrasten Color to fill cursor area with Farbe für die Auswahl-Markierung None Keine Background Hintergrundfarbe Dots Farbige Abtastpunkte Conversion threshold display mode (analog traces only) Darstellung von Konvertierungsschwellen (nur für analoge Kanäle) Default analog trace div height Standardgröße von analogen Kanälen Default logic trace height Standardgröße von Logikkanälen Allow configuration of &initial signal state &Initialzustände von Signalen konfigurierbar machen Always show all &rows, even if no annotation is visible Immer alle &Reihen anzeigen, auch wenn hierfür keine dekodierten Werte vorliegen Annotation export format Format für zu exportierende Dekodierwerte %s = sample range; %d: decoder name; %r: row name; %c: class name %s = Start-/Endsample; %d: Dekodername; %r: Name der Reihe; %c: Klassenname %1: longest annotation text; %a: all annotation texts; %q: use quotation marks %1: Längste Beschreibung des dekodierten Wertes; %a: Alle Beschreibungen des dekodierten Wertes; %q: Benutze Anführungszeichen %s = sample range; %d: decoder name; %r: row name; %q: use quotation marks %s = Start-/Endsample; %d: Dekodername; %c Name der Kategorie; %q: Benutze Anführungszeichen %s = sample range; %d: decoder name; %c: row name; %q: use quotations marks %s = Start-/Endsample; %d: Dekodername; %c Name der Kategorie; %q: Benutze Anführungszeichen %1: longest annotation text; %a: all annotation texts %1: Längste Beschreibung des dekodierten Wertes; %a: Alle Beschreibungen des dekodierten Wertes %1<br /><a href="http://%2">%2</a> GNU GPL, version 3 or later GNU GPL, Version 3 oder neuer Versions, libraries and features: Versionen, Bibliotheken und Features: Firmware search paths: Suchpfade für Firmware: Protocol decoder search paths: Suchpfade für Protokolldekoder: Supported hardware drivers: Unterstützte Hardwaretreiber: Supported input formats: Unterstützte Importformate: Supported output formats: Unterstützte Exportformate: Supported protocol decoders: Unterstützte Protokolldekoder: Log level: Log-Level: lines Zeilen Length of background buffer: Länge des Logpuffers: &Save to File &Speichern &Pop out &Abdocken You selected a dark theme. Should I set the user-adjustable colors to better suit your choice? Please keep in mind that PulseView may need a restart to display correctly. Es wurde ein dunkles Design gewählt. Sollen die benutzerspezifischen Farben entsprechend angepasst werden, damit sie besser harmonieren? Bei einer Änderung benötigt PulseView eventuell einen Neustart, damit alles korrekt angezeigt wird. You selected a bright theme. Should I set the user-adjustable colors to better suit your choice? Please keep in mind that PulseView may need a restart to display correctly. Es wurde ein helles Design gewählt. Sollen die benutzerspezifischen Farben entsprechend angepasst werden, damit sie besser harmonieren? Bei einer Änderung benötigt PulseView eventuell einen Neustart, damit alles korrekt angezeigt wird. Save Log Log speichern Log Files (*.txt *.log);;All Files (*) Logdateien (*.txt *.log);;Alle Dateien (*) Success Erfolg Log saved to %1. Log als %1 gespeichert. Error Fehler File %1 could not be written to. Konnte Datei %1 nicht speichern. %1 Log pv::dialogs::StoreProgress Saving... Speichere... Cancel Abbrechen Failed to save session. Beim Speichern trat ein Fehler auf. pv::popups::Channels All Alle Logic Logik Analog Analog Named Benamte Unnamed Unbenamte Changing Sich ändernde Non-changing Konstante Disable: Deaktivieren: Enable: Aktivieren: None Keine pv::prop::Bool Querying config key %1 resulted in %2 Internal message pv::prop::Double Querying config key %1 resulted in %2 Internal message pv::prop::Enum Querying config key %1 resulted in %2 Internal message pv::prop::Int Querying config key %1 resulted in %2 Internal message pv::prop::String Querying config key %1 resulted in %2 Internal message pv::subwindows::decoder_selector::DecoderCollectionModel Decoder Dekoder Name ID All Decoders Alle Dekoder pv::subwindows::decoder_selector::SubWindow Select a decoder to see its description here. Wähle einen Dekoder, um dessen Beschreibung hier lesen zu können. , %1 <p align='right'>Tags: %1</p> <p align='right'>Stichworte: %1</p> Protocol decoder <b>%1</b> requires input type <b>%2</b> which several decoders provide.<br>Choose which one to use:<br> Protokolldekoder <b>%1</b> benötigt Daten vom Typ <b>%2</b>, die von verschiedenen Protokolldekodern bereitgestellt werden. <br>Wähle, welcher benutzt werden soll:<br> Choose Decoder Wähle Protokolldekoder pv::toolbars::MainBar New &View Neue &Ansicht &Open... &Öffnen... Restore Session Setu&p... &Konfiguration der Analysesitzung laden... &Save As... &Speichern als... Save Selected &Range As... Ausgewählten &Bereich speichern als... Save Session Setu&p... &Konfiguration der Analysesitzung speichern... &Export &Import &Connect to Device... Mit Gerät &verbinden... Add protocol decoder Protokolldekoder hinzufügen Configure Device Gerät konfigurieren Configure Channels Kanäle konfigurieren Failed to get sample rate list: Konnte Liste unterstützter Abtastraten nicht abfragen: Failed to get sample rate: Konnte Abtastrate nicht abfragen: Failed to get sample limit list: Konnte Liste der maximal erlaubten Abtastraten nicht abfragen: Failed to configure samplerate: Konnte Abtastrate nicht einstellen: Failed to configure sample count: Konnte Anzahl der Abtastpunkte nicht einstellen: Missing Cursors Fehlende Auswahl You need to set the cursors before you can save the data enclosed by them to a session file (e.g. using the Show Cursors button). Du musst die Auswahl-Markierer setzen, bevor du die darin befindlichen Daten abspeichern kannst. Verwende hierzu bspw. den Knopf für die Auswahl-Markierer. Invalid Range Auswahl ungültig The cursors don't define a valid range of samples. Die Auswahl-Markierer geben keinen gültigen Datenbereich an. %1 files %1-Dateien All Files Alle Dateien Save File Speichern Export %1 %1 exportieren %1 files %1-Dateien Import File Dateiimport Import %1 %1 importieren Open File Öffnen sigrok Sessions (*.sr);;All Files (*) sigrok-Datenformat (*.sr);;Alle Dateien (*) PulseView Session Setups (*.pvs);;All Files (*) Analysesitzungs-Konfigurationen (*.pvs);;Alle Dateien (*) Total sampling time: %1 Internal message pv::views::decoder_binary::View Decoder: Dekoder: Show data as Zeige Daten als Hexdump Hex-Dump &Save... &Speichern... Save Binary Data Binäre Daten speichern Binary Data Files (*.bin);;All Files (*) Binärdateien (*.bin);;Alle Dateien (*) Error Fehler File %1 could not be written to. Konnte Datei %1 nicht speichern. Hex Dumps (*.txt);;All Files (*) Hex-Dumps (*.txt);;Alle Dateien (*) pv::views::decoder_output::View Decoder: Dekoder: Show data as Zeige Daten als Hexdump Hex-Dump &Save... &Speichern... Save Binary Data Binäre Daten speichern Binary Data Files (*.bin);;All Files (*) Binärdateien (*.bin);;Alle Dateien (*) Error Fehler File %1 could not be written to. Konnte Datei %1 nicht speichern. Hex Dumps (*.txt);;All Files (*) Hex-Dumps (*.txt);;Alle Dateien (*) pv::views::trace::AnalogSignal Number of pos vertical divs Anzahl Unterteilungen im Positiven Number of neg vertical divs Anzahl Unterteilungen im Negativen pixels Pixel Div height Höhe einer Unterteilung V/div V/div Vertical resolution Vertikale Auflösung Autoranging Automatische Skalierung none keine to logic via threshold zu Logik mittels Schwellwert to logic via schmitt-trigger zu Logik mittels Schmitt-Trigger Conversion Konvertierung Conversion threshold(s) Konvertierungs-Schwellwert(e) analog nur analog converted nur konvertiert analog+converted analog+konvertiert Show traces for Anzuzeigende Signale pv::views::trace::Cursor Disable snapping Einrasten deaktivieren pv::views::trace::CursorPair Display interval Intervall anzeigen Display frequency Frequenz anzeigen Display samples Samples anzeigen pv::views::trace::DecodeTrace <p><i>No decoders in the stack</i></p> <p><i>Keine Protokolldekoder vorhanden</i></p> <i>* Required channels</i> <i>* Notwendige Kanäle</i> Stack Decoder Protokolldekoder stapeln Stack a higher-level decoder on top of this one Weiteren Protokolldekoder auf diesen stapeln Delete Löschen Resume decoding Dekodierung fortsetzen Pause decoding Dekodierung anhalten Copy annotation text to clipboard Dekodierten Wert in die Zwischenablage kopieren Export all annotations Alle dekodierten Werte exportieren Export all annotations for this row Alle dekodierten Werte dieser Zeile exportieren Export all annotations, starting here Alle dekodierten Werte ab hier exportieren Export annotations for this row, starting here Alle dekodierten Werte dieser Zeile ab hier exportieren Export all annotations within cursor range Alle dekodierten Werte innerhalb des gewählten Bereiches exportieren Export annotations for this row within cursor range Alle dekodierten Werte dieser Zeile innerhalb des gewählten Bereiches exportieren %1: %2 <b>%1</b> (%2) %3 Export annotations Dekodierte Werte exportieren Text Files (*.txt);;All Files (*) Textdateien (*.txt);;Alle Dateien (*) Error Fehler File %1 could not be written to. Konnte Datei %1 nicht speichern. Show this row Diese Zeile anzeigen Show All Alle anzeigen Hide All Alle verstecken pv::views::trace::Flag Text Delete Löschen Disable snapping Einrasten deaktivieren pv::views::trace::Header Group Gruppieren pv::views::trace::LogicSignal No trigger Kein Trigger Trigger on rising edge Trigger auf steigende Flanke Trigger on high level Trigger auf High-Pegel Trigger on falling edge Trigger auf fallende Flanke Trigger on low level Trigger auf Low-Pegel Trigger on rising or falling edge Trigger auf steigende oder fallende Flanke pixels Pixel Trace height Kanalgröße Trigger Trigger pv::views::trace::Ruler Create marker here Hier neue Markierung anlegen Set as zero point Als Nullpunkt setzen Reset zero point Nullpunkt zurücksetzen Disable mouse hover marker Mauszeigerbalken deaktivieren Enable mouse hover marker Mauszeigerbalken aktivieren pv::views::trace::Signal Name Disable Deaktivieren pv::views::trace::StandardBar Zoom &In H&ineinzoomen Zoom &Out Hera&uszoomen Zoom to &Fit &Passend zoomen Show &Cursors &Auswahl-Markierer anzeigen Display last segment only Nur letztes Segment anzeigen Display last complete segment only Nur letztes vollständiges Segment anzeigen Display a single segment Einzelnes Segment anzeigen pv::views::trace::TimeMarker Time Zeit pv::views::trace::Trace Create marker here Hier neue Markierung anlegen Color Farbe Name pv::views::trace::TraceGroup Ungroup Trennen pv::widgets::DecoderGroupBox Show/hide this decoder trace Dekoder anzeigen/verbergen Delete this decoder trace Protokolldekoder entfernen pv::widgets::DeviceToolButton <No Device> <Kein Gerät> pv::widgets::ExportMenu Export %1... %1 exportieren... pv::widgets::ImportMenu Import %1... %1 importieren... pulseview-0.4.2/pv/000700 001750 001750 00000000000 13640725776 013636 5ustar00uweuwe000000 000000 pulseview-0.4.2/pv/strnatcmp.hpp000600 001750 001750 00000007162 13640725356 016364 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.2/pv/application.hpp000600 001750 001750 00000004414 13640725356 016651 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 #include #include #include "globalsettings.hpp" using std::shared_ptr; using std::pair; using std::vector; class Application : public QApplication, public pv::GlobalSettingsInterface { Q_OBJECT public: Application(int &argc, char* argv[]); QStringList get_languages(); void switch_language(const QString& language); void on_setting_changed(const QString &key, const QVariant &value); 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_; QTranslator app_translator_, qt_translator_, qtbase_translator_; }; #endif // PULSEVIEW_PV_APPLICATION_HPP pulseview-0.4.2/pv/views/000700 001750 001750 00000000000 13640725776 014773 5ustar00uweuwe000000 000000 pulseview-0.4.2/pv/views/viewbase.cpp000600 001750 001750 00000010736 13640725356 017307 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 char* ViewTypeNames[ViewTypeCount] = { "Trace View", #ifdef ENABLE_DECODE "Binary Decoder Output View" #endif }; const int ViewBase::MaxViewAutoUpdateRate = 25; // No more than 25 Hz ViewBase::ViewBase(Session &session, bool is_main_view, QMainWindow *parent) : // Note: Place defaults in ViewBase::reset_view_state(), not here QWidget(parent), 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); } bool ViewBase::is_main_view() const { return is_main_view_; } void ViewBase::reset_view_state() { current_segment_ = 0; } Session& ViewBase::session() { return session_; } const Session& ViewBase::session() const { return session_; } void ViewBase::clear_signals() { clear_signalbases(); } vector< 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_.push_back(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() { decode_signals_.clear(); } void ViewBase::add_decode_signal(shared_ptr signal) { decode_signals_.push_back(signal); } void ViewBase::remove_decode_signal(shared_ptr signal) { decode_signals_.erase(std::remove_if( decode_signals_.begin(), decode_signals_.end(), [&](shared_ptr s) { return s == signal; }), decode_signals_.end()); } #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.2/pv/views/decoder_binary/000700 001750 001750 00000000000 13640725776 017744 5ustar00uweuwe000000 000000 pulseview-0.4.2/pv/views/decoder_binary/QHexView.cpp000600 001750 001750 00000044545 13640725356 022160 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2015 Victor Anjin * Copyright (C) 2019 Soeren Apel * * The MIT License (MIT) * * Copyright (c) 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "QHexView.hpp" const unsigned int BYTES_PER_LINE = 16; const unsigned int HEXCHARS_IN_LINE = BYTES_PER_LINE * 3 - 1; const unsigned int GAP_ADR_HEX = 10; const unsigned int GAP_HEX_ASCII = 10; const unsigned int GAP_ASCII_SLIDER = 5; QHexView::QHexView(QWidget *parent): QAbstractScrollArea(parent), mode_(ChunkedDataMode), data_(nullptr), selectBegin_(0), selectEnd_(0), cursorPos_(0) { setFont(QFont("Courier", 10)); charWidth_ = fontMetrics().boundingRect('X').width(); charHeight_ = fontMetrics().height(); // Determine X coordinates of the three sub-areas posAddr_ = 0; posHex_ = 10 * charWidth_ + GAP_ADR_HEX; posAscii_ = posHex_ + HEXCHARS_IN_LINE * charWidth_ + GAP_HEX_ASCII; setFocusPolicy(Qt::StrongFocus); if (palette().color(QPalette::ButtonText).toHsv().value() > 127) { // Color is bright chunk_colors_.emplace_back(100, 149, 237); // QColorConstants::Svg::cornflowerblue chunk_colors_.emplace_back(60, 179, 113); // QColorConstants::Svg::mediumseagreen chunk_colors_.emplace_back(210, 180, 140); // QColorConstants::Svg::tan } else { // Color is dark chunk_colors_.emplace_back(0, 0, 139); // QColorConstants::Svg::darkblue chunk_colors_.emplace_back(34, 139, 34); // QColorConstants::Svg::forestgreen chunk_colors_.emplace_back(160, 82, 45); // QColorConstants::Svg::sienna } } void QHexView::set_mode(Mode m) { mode_ = m; // This is not expected to be set when data is showing, // so we don't update the viewport here } void QHexView::set_data(const DecodeBinaryClass* data) { data_ = data; size_t size = 0; if (data) { size_t chunks = data_->chunks.size(); for (size_t i = 0; i < chunks; i++) size += data_->chunks[i].data.size(); } data_size_ = size; viewport()->update(); } unsigned int QHexView::get_bytes_per_line() const { return BYTES_PER_LINE; } void QHexView::clear() { verticalScrollBar()->setValue(0); data_ = nullptr; data_size_ = 0; viewport()->update(); } void QHexView::showFromOffset(size_t offset) { if (data_ && (offset < data_size_)) { setCursorPos(offset * 2); int cursorY = cursorPos_ / (2 * BYTES_PER_LINE); verticalScrollBar() -> setValue(cursorY); } viewport()->update(); } QSizePolicy QHexView::sizePolicy() const { return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); } pair QHexView::get_selection() const { size_t start = selectBegin_ / 2; size_t end = selectEnd_ / 2; if (start == end) { // Nothing is currently selected start = 0; end = data_size_; } if (end < data_size_) end++; return std::make_pair(start, end); } size_t QHexView::create_hex_line(size_t start, size_t end, QString* dest, bool with_offset, bool with_ascii) { dest->clear(); // Determine start address for the row uint64_t row = start / BYTES_PER_LINE; uint64_t offset = row * BYTES_PER_LINE; end = std::min((uint64_t)end, offset + BYTES_PER_LINE); if (with_offset) dest->append(QString("%1 ").arg(row * BYTES_PER_LINE, 10, 16, QChar('0')).toUpper()); initialize_byte_iterator(offset); for (size_t i = offset; i < offset + BYTES_PER_LINE; i++) { uint8_t value = 0; if (i < end) value = get_next_byte(); if ((i < start) || (i >= end)) dest->append(" "); else dest->append(QString("%1 ").arg(value, 2, 16, QChar('0')).toUpper()); } if (with_ascii) { initialize_byte_iterator(offset); for (size_t i = offset; i < end; i++) { uint8_t value = get_next_byte(); if ((value < 0x20) || (value > 0x7E)) value = '.'; if (i < start) dest->append(' '); else dest->append((char)value); } } return end; } void QHexView::initialize_byte_iterator(size_t offset) { current_chunk_id_ = 0; current_chunk_offset_ = 0; current_offset_ = offset; size_t chunks = data_->chunks.size(); for (size_t i = 0; i < chunks; i++) { size_t size = data_->chunks[i].data.size(); if (offset >= size) { current_chunk_id_++; offset -= size; } else { current_chunk_offset_ = offset; break; } } if (current_chunk_id_ < data_->chunks.size()) current_chunk_ = data_->chunks[current_chunk_id_]; } uint8_t QHexView::get_next_byte(bool* is_next_chunk) { if (is_next_chunk != nullptr) *is_next_chunk = (current_chunk_offset_ == 0); uint8_t v = 0; if (current_chunk_offset_ < current_chunk_.data.size()) v = current_chunk_.data[current_chunk_offset_]; current_offset_++; current_chunk_offset_++; if (current_offset_ > data_size_) { qWarning() << "QHexView::get_next_byte() overran binary data boundary:" << current_offset_ << "of" << data_size_ << "bytes"; return 0xEE; } if ((current_chunk_offset_ == current_chunk_.data.size()) && (current_offset_ < data_size_)) { current_chunk_id_++; current_chunk_offset_ = 0; current_chunk_ = data_->chunks[current_chunk_id_]; } return v; } QSize QHexView::getFullSize() const { size_t width = posAscii_ + (BYTES_PER_LINE * charWidth_); if (verticalScrollBar()->isEnabled()) width += GAP_ASCII_SLIDER + verticalScrollBar()->width(); if (!data_ || (data_size_ == 0)) return QSize(width, 0); size_t height = data_size_ / BYTES_PER_LINE; if (data_size_ % BYTES_PER_LINE) height++; height *= charHeight_; return QSize(width, height); } void QHexView::paintEvent(QPaintEvent *event) { QPainter painter(viewport()); // Calculate and update the widget and paint area sizes QSize widgetSize = getFullSize(); setMinimumWidth(widgetSize.width()); setMaximumWidth(widgetSize.width()); QSize areaSize = viewport()->size() - QSize(0, charHeight_); // Only show scrollbar if the content goes beyond the visible area if (widgetSize.height() > areaSize.height()) { verticalScrollBar()->setEnabled(true); verticalScrollBar()->setPageStep(areaSize.height() / charHeight_); verticalScrollBar()->setRange(0, ((widgetSize.height() - areaSize.height())) / charHeight_ + 1); } else verticalScrollBar()->setEnabled(false); // Fill widget background painter.fillRect(event->rect(), palette().color(QPalette::Base)); if (!data_ || (data_size_ == 0) || (data_->chunks.empty())) { painter.setPen(palette().color(QPalette::Text)); QString s = tr("No data available"); int x = (areaSize.width() - fontMetrics().boundingRect(s).width()) / 2; int y = areaSize.height() / 2; painter.drawText(x, y, s); return; } // Determine first/last line indices size_t firstLineIdx = verticalScrollBar()->value(); size_t lastLineIdx = firstLineIdx + (areaSize.height() / charHeight_); if (lastLineIdx > (data_size_ / BYTES_PER_LINE)) { lastLineIdx = data_size_ / BYTES_PER_LINE; if (data_size_ % BYTES_PER_LINE) lastLineIdx++; } // Paint divider line between hex and ASCII areas int line_x = posAscii_ - (GAP_HEX_ASCII / 2); painter.setPen(palette().color(QPalette::Midlight)); painter.drawLine(line_x, event->rect().top(), line_x, height()); // Fill address area background painter.fillRect(QRect(posAddr_, event->rect().top(), posHex_ - (GAP_ADR_HEX / 2), height()), palette().color(QPalette::Window)); painter.fillRect(QRect(posAddr_, event->rect().top(), posAscii_ - (GAP_HEX_ASCII / 2), charHeight_ + 2), palette().color(QPalette::Window)); // Paint address area painter.setPen(palette().color(QPalette::ButtonText)); int yStart = 2 * charHeight_; for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) { QString address = QString("%1").arg(lineIdx * 16, 10, 16, QChar('0')).toUpper(); painter.drawText(posAddr_, y, address); y += charHeight_; } // Paint top row with hex offsets painter.setPen(palette().color(QPalette::ButtonText)); for (int offset = 0; offset <= 0xF; offset++) painter.drawText(posHex_ + (1 + offset * 3) * charWidth_, charHeight_ - 3, QString::number(offset, 16).toUpper()); // Paint hex values QBrush regular = palette().buttonText(); QBrush selected = palette().highlight(); bool multiple_chunks = (data_->chunks.size() > 1); unsigned int chunk_color = 0; initialize_byte_iterator(firstLineIdx * BYTES_PER_LINE); yStart = 2 * charHeight_; for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) { int x = posHex_; for (size_t i = 0; (i < BYTES_PER_LINE) && (current_offset_ < data_size_); i++) { size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2; // Fetch byte bool is_next_chunk; uint8_t byte_value = get_next_byte(&is_next_chunk); if (is_next_chunk) { chunk_color++; if (chunk_color == chunk_colors_.size()) chunk_color = 0; } if ((pos >= selectBegin_) && (pos < selectEnd_)) { painter.setBackgroundMode(Qt::OpaqueMode); painter.setBackground(selected); painter.setPen(palette().color(QPalette::HighlightedText)); } else { painter.setBackground(regular); painter.setBackgroundMode(Qt::TransparentMode); if (!multiple_chunks) painter.setPen(palette().color(QPalette::Text)); else painter.setPen(chunk_colors_[chunk_color]); } // First nibble QString val = QString::number((byte_value & 0xF0) >> 4, 16).toUpper(); painter.drawText(x, y, val); // Second nibble val = QString::number((byte_value & 0xF), 16).toUpper(); painter.drawText(x + charWidth_, y, val); if ((pos >= selectBegin_) && (pos < selectEnd_ - 1) && (i < BYTES_PER_LINE - 1)) painter.drawText(x + 2 * charWidth_, y, QString(' ')); x += 3 * charWidth_; } y += charHeight_; } // Paint ASCII characters initialize_byte_iterator(firstLineIdx * BYTES_PER_LINE); yStart = 2 * charHeight_; for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) { int x = posAscii_; for (size_t i = 0; (i < BYTES_PER_LINE) && (current_offset_ < data_size_); i++) { // Fetch byte uint8_t ch = get_next_byte(); if ((ch < 0x20) || (ch > 0x7E)) ch = '.'; size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2; if ((pos >= selectBegin_) && (pos < selectEnd_)) { painter.setBackgroundMode(Qt::OpaqueMode); painter.setBackground(selected); painter.setPen(palette().color(QPalette::HighlightedText)); } else { painter.setBackgroundMode(Qt::TransparentMode); painter.setBackground(regular); painter.setPen(palette().color(QPalette::Text)); } painter.drawText(x, y, QString(ch)); x += charWidth_; } y += charHeight_; } // Paint cursor if (hasFocus()) { int x = (cursorPos_ % (2 * BYTES_PER_LINE)); int y = cursorPos_ / (2 * BYTES_PER_LINE); y -= firstLineIdx; int cursorX = (((x / 2) * 3) + (x % 2)) * charWidth_ + posHex_; int cursorY = charHeight_ + y * charHeight_ + 4; painter.fillRect(cursorX, cursorY, 2, charHeight_, palette().color(QPalette::WindowText)); } } void QHexView::keyPressEvent(QKeyEvent *event) { bool setVisible = false; // Cursor movements if (event->matches(QKeySequence::MoveToNextChar)) { setCursorPos(cursorPos_ + 1); resetSelection(cursorPos_); setVisible = true; } if (event->matches(QKeySequence::MoveToPreviousChar)) { setCursorPos(cursorPos_ - 1); resetSelection(cursorPos_); setVisible = true; } if (event->matches(QKeySequence::MoveToEndOfLine)) { setCursorPos(cursorPos_ | ((BYTES_PER_LINE * 2) - 1)); resetSelection(cursorPos_); setVisible = true; } if (event->matches(QKeySequence::MoveToStartOfLine)) { setCursorPos(cursorPos_ | (cursorPos_ % (BYTES_PER_LINE * 2))); resetSelection(cursorPos_); setVisible = true; } if (event->matches(QKeySequence::MoveToPreviousLine)) { setCursorPos(cursorPos_ - BYTES_PER_LINE * 2); resetSelection(cursorPos_); setVisible = true; } if (event->matches(QKeySequence::MoveToNextLine)) { setCursorPos(cursorPos_ + BYTES_PER_LINE * 2); resetSelection(cursorPos_); setVisible = true; } if (event->matches(QKeySequence::MoveToNextPage)) { setCursorPos(cursorPos_ + (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE); resetSelection(cursorPos_); setVisible = true; } if (event->matches(QKeySequence::MoveToPreviousPage)) { setCursorPos(cursorPos_ - (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE); resetSelection(cursorPos_); setVisible = true; } if (event->matches(QKeySequence::MoveToEndOfDocument)) { setCursorPos(data_size_ * 2); resetSelection(cursorPos_); setVisible = true; } if (event->matches(QKeySequence::MoveToStartOfDocument)) { setCursorPos(0); resetSelection(cursorPos_); setVisible = true; } // Select commands if (event->matches(QKeySequence::SelectAll)) { resetSelection(0); setSelection(2 * data_size_); setVisible = true; } if (event->matches(QKeySequence::SelectNextChar)) { int pos = cursorPos_ + 1; setCursorPos(pos); setSelection(pos); setVisible = true; } if (event->matches(QKeySequence::SelectPreviousChar)) { int pos = cursorPos_ - 1; setSelection(pos); setCursorPos(pos); setVisible = true; } if (event->matches(QKeySequence::SelectEndOfLine)) { int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE); setCursorPos(pos); setSelection(pos); setVisible = true; } if (event->matches(QKeySequence::SelectStartOfLine)) { int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE)); setCursorPos(pos); setSelection(pos); setVisible = true; } if (event->matches(QKeySequence::SelectPreviousLine)) { int pos = cursorPos_ - (2 * BYTES_PER_LINE); setCursorPos(pos); setSelection(pos); setVisible = true; } if (event->matches(QKeySequence::SelectNextLine)) { int pos = cursorPos_ + (2 * BYTES_PER_LINE); setCursorPos(pos); setSelection(pos); setVisible = true; } if (event->matches(QKeySequence::SelectNextPage)) { int pos = cursorPos_ + (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE); setCursorPos(pos); setSelection(pos); setVisible = true; } if (event->matches(QKeySequence::SelectPreviousPage)) { int pos = cursorPos_ - (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE); setCursorPos(pos); setSelection(pos); setVisible = true; } if (event->matches(QKeySequence::SelectEndOfDocument)) { int pos = data_size_ * 2; setCursorPos(pos); setSelection(pos); setVisible = true; } if (event->matches(QKeySequence::SelectStartOfDocument)) { setCursorPos(0); setSelection(0); setVisible = true; } if (event->matches(QKeySequence::Copy) && (data_)) { QString text; initialize_byte_iterator(selectBegin_ / 2); size_t selectedSize = (selectEnd_ - selectBegin_ + 1) / 2; for (size_t i = 0; i < selectedSize; i++) { uint8_t byte_value = get_next_byte(); QString s = QString::number((byte_value & 0xF0) >> 4, 16).toUpper() + QString::number((byte_value & 0xF), 16).toUpper() + " "; text += s; if (i % BYTES_PER_LINE == (BYTES_PER_LINE - 1)) text += "\n"; } QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(text, QClipboard::Clipboard); if (clipboard->supportsSelection()) clipboard->setText(text, QClipboard::Selection); } if (setVisible) ensureVisible(); viewport()->update(); } void QHexView::mouseMoveEvent(QMouseEvent *event) { int actPos = cursorPosFromMousePos(event->pos()); setCursorPos(actPos); setSelection(actPos); viewport()->update(); } void QHexView::mousePressEvent(QMouseEvent *event) { int cPos = cursorPosFromMousePos(event->pos()); if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) && (event->button() == Qt::LeftButton)) setSelection(cPos); else resetSelection(cPos); setCursorPos(cPos); viewport()->update(); } size_t QHexView::cursorPosFromMousePos(const QPoint &position) { size_t pos = -1; if (((size_t)position.x() >= posHex_) && ((size_t)position.x() < (posHex_ + HEXCHARS_IN_LINE * charWidth_))) { // Note: We add 1.5 character widths so that selection across // byte gaps is smoother size_t x = (position.x() + (1.5 * charWidth_ / 2) - posHex_) / charWidth_; // Note: We allow only full bytes to be selected, not nibbles, // so we round to the nearest byte gap x = (2 * x + 1) / 3; size_t firstLineIdx = verticalScrollBar()->value(); size_t y = ((position.y() / charHeight_) - 1) * 2 * BYTES_PER_LINE; pos = x + y + firstLineIdx * BYTES_PER_LINE * 2; } size_t max_pos = data_size_ * 2; return std::min(pos, max_pos); } void QHexView::resetSelection() { selectBegin_ = selectInit_; selectEnd_ = selectInit_; } void QHexView::resetSelection(int pos) { if (pos < 0) pos = 0; selectInit_ = pos; selectBegin_ = pos; selectEnd_ = pos; } void QHexView::setSelection(int pos) { if (pos < 0) pos = 0; if ((size_t)pos >= selectInit_) { selectEnd_ = pos; selectBegin_ = selectInit_; } else { selectBegin_ = pos; selectEnd_ = selectInit_; } } void QHexView::setCursorPos(int position) { if (position < 0) position = 0; int max_pos = data_size_ * 2; if (position > max_pos) position = max_pos; cursorPos_ = position; } void QHexView::ensureVisible() { QSize areaSize = viewport()->size(); int firstLineIdx = verticalScrollBar()->value(); int lastLineIdx = firstLineIdx + areaSize.height() / charHeight_; int cursorY = cursorPos_ / (2 * BYTES_PER_LINE); if (cursorY < firstLineIdx) verticalScrollBar()->setValue(cursorY); else if(cursorY >= lastLineIdx) verticalScrollBar()->setValue(cursorY - areaSize.height() / charHeight_ + 1); } pulseview-0.4.2/pv/views/decoder_binary/QHexView.hpp000600 001750 001750 00000006223 13640725356 022154 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2015 Victor Anjin * Copyright (C) 2019 Soeren Apel * * The MIT License (MIT) * * Copyright (c) 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef PULSEVIEW_PV_VIEWS_DECODERBINARY_QHEXVIEW_H #define PULSEVIEW_PV_VIEWS_DECODERBINARY_QHEXVIEW_H #include #include using std::pair; using std::size_t; using pv::data::DecodeBinaryClass; using pv::data::DecodeBinaryDataChunk; class QHexView: public QAbstractScrollArea { Q_OBJECT public: enum Mode { ChunkedDataMode, ///< Displays all data chunks in succession MemoryEmulationMode ///< Reconstructs memory contents from data chunks }; public: QHexView(QWidget *parent = nullptr); void set_mode(Mode m); void set_data(const DecodeBinaryClass* data); unsigned int get_bytes_per_line() const; void clear(); void showFromOffset(size_t offset); virtual QSizePolicy sizePolicy() const; pair get_selection() const; size_t create_hex_line(size_t start, size_t end, QString* dest, bool with_offset=false, bool with_ascii=false); protected: void initialize_byte_iterator(size_t offset); uint8_t get_next_byte(bool* is_next_chunk = nullptr); void paintEvent(QPaintEvent *event); void keyPressEvent(QKeyEvent *event); void mouseMoveEvent(QMouseEvent *event); void mousePressEvent(QMouseEvent *event); private: QSize getFullSize() const; void resetSelection(); void resetSelection(int pos); void setSelection(int pos); void ensureVisible(); void setCursorPos(int pos); size_t cursorPosFromMousePos(const QPoint &position); private: Mode mode_; const DecodeBinaryClass* data_; size_t data_size_; size_t posAddr_, posHex_, posAscii_; size_t charWidth_, charHeight_; size_t selectBegin_, selectEnd_, selectInit_, cursorPos_; size_t current_chunk_id_, current_chunk_offset_, current_offset_; DecodeBinaryDataChunk current_chunk_; // Cache locally so that we're not messed up when the vector is re-allocating its data vector chunk_colors_; }; #endif /* PULSEVIEW_PV_VIEWS_DECODERBINARY_QHEXVIEW_H */ pulseview-0.4.2/pv/views/decoder_binary/view.cpp000600 001750 001750 00000032133 13640725356 021420 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2019 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 #include #include #include #include #include #include #include "view.hpp" #include "QHexView.hpp" #include "pv/globalsettings.hpp" #include "pv/session.hpp" #include "pv/util.hpp" #include "pv/data/decode/decoder.hpp" using pv::data::DecodeSignal; using pv::data::SignalBase; using pv::data::decode::Decoder; using pv::util::Timestamp; using std::shared_ptr; namespace pv { namespace views { namespace decoder_binary { const char* SaveTypeNames[SaveTypeCount] = { "Binary", "Hex Dump, plain", "Hex Dump, with offset", "Hex Dump, canonical" }; View::View(Session &session, bool is_main_view, QMainWindow *parent) : ViewBase(session, is_main_view, parent), // Note: Place defaults in View::reset_view_state(), not here parent_(parent), decoder_selector_(new QComboBox()), format_selector_(new QComboBox()), class_selector_(new QComboBox()), stacked_widget_(new QStackedWidget()), hex_view_(new QHexView()), save_button_(new QToolButton()), save_action_(new QAction(this)), signal_(nullptr) { QVBoxLayout *root_layout = new QVBoxLayout(this); root_layout->setContentsMargins(0, 0, 0, 0); // Create toolbar QToolBar* toolbar = new QToolBar(); toolbar->setContextMenuPolicy(Qt::PreventContextMenu); parent->addToolBar(toolbar); // Populate toolbar toolbar->addWidget(new QLabel(tr("Decoder:"))); toolbar->addWidget(decoder_selector_); toolbar->addWidget(class_selector_); toolbar->addSeparator(); toolbar->addWidget(new QLabel(tr("Show data as"))); toolbar->addWidget(format_selector_); toolbar->addSeparator(); toolbar->addWidget(save_button_); // Add format types format_selector_->addItem(tr("Hexdump"), qVariantFromValue(QString("text/hexdump"))); // Add widget stack root_layout->addWidget(stacked_widget_); stacked_widget_->addWidget(hex_view_); stacked_widget_->setCurrentIndex(0); connect(decoder_selector_, SIGNAL(currentIndexChanged(int)), this, SLOT(on_selected_decoder_changed(int))); connect(class_selector_, SIGNAL(currentIndexChanged(int)), this, SLOT(on_selected_class_changed(int))); // Configure widgets decoder_selector_->setSizeAdjustPolicy(QComboBox::AdjustToContents); class_selector_->setSizeAdjustPolicy(QComboBox::AdjustToContents); // Configure actions save_action_->setText(tr("&Save...")); save_action_->setIcon(QIcon::fromTheme("document-save-as", QIcon(":/icons/document-save-as.png"))); save_action_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S)); connect(save_action_, SIGNAL(triggered(bool)), this, SLOT(on_actionSave_triggered())); QMenu *save_menu = new QMenu(); connect(save_menu, SIGNAL(triggered(QAction*)), this, SLOT(on_actionSave_triggered(QAction*))); for (int i = 0; i < SaveTypeCount; i++) { QAction *const action = save_menu->addAction(tr(SaveTypeNames[i])); action->setData(qVariantFromValue(i)); } save_button_->setMenu(save_menu); save_button_->setDefaultAction(save_action_); save_button_->setPopupMode(QToolButton::MenuButtonPopup); parent->setSizePolicy(hex_view_->sizePolicy()); // TODO Must be updated when selected widget changes reset_view_state(); } ViewType View::get_type() const { return ViewTypeDecoderBinary; } void View::reset_view_state() { ViewBase::reset_view_state(); decoder_selector_->clear(); class_selector_->clear(); format_selector_->setCurrentIndex(0); save_button_->setEnabled(false); hex_view_->clear(); } void View::clear_decode_signals() { ViewBase::clear_decode_signals(); reset_data(); reset_view_state(); } void View::add_decode_signal(shared_ptr signal) { ViewBase::add_decode_signal(signal); connect(signal.get(), SIGNAL(name_changed(const QString&)), this, SLOT(on_signal_name_changed(const QString&))); connect(signal.get(), SIGNAL(decoder_stacked(void*)), this, SLOT(on_decoder_stacked(void*))); connect(signal.get(), SIGNAL(decoder_removed(void*)), this, SLOT(on_decoder_removed(void*))); // Add all decoders provided by this signal auto stack = signal->decoder_stack(); if (stack.size() > 1) { for (const shared_ptr& dec : stack) // Only add the decoder if it has binary output if (dec->get_binary_class_count() > 0) { QString title = QString("%1 (%2)").arg(signal->name(), dec->name()); decoder_selector_->addItem(title, QVariant::fromValue((void*)dec.get())); } } else if (!stack.empty()) { shared_ptr& dec = stack.at(0); if (dec->get_binary_class_count() > 0) decoder_selector_->addItem(signal->name(), QVariant::fromValue((void*)dec.get())); } } void View::remove_decode_signal(shared_ptr signal) { // Remove all decoders provided by this signal for (const shared_ptr& dec : signal->decoder_stack()) { int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get())); if (index != -1) decoder_selector_->removeItem(index); } ViewBase::remove_decode_signal(signal); if (signal.get() == signal_) { reset_data(); update_data(); reset_view_state(); } } void View::save_settings(QSettings &settings) const { (void)settings; } void View::restore_settings(QSettings &settings) { // Note: It is assumed that this function is only called once, // immediately after restoring a previous session. (void)settings; } void View::reset_data() { signal_ = nullptr; decoder_ = nullptr; bin_class_id_ = 0; binary_data_exists_ = false; hex_view_->clear(); } void View::update_data() { if (!signal_) return; const DecodeBinaryClass* bin_class = signal_->get_binary_data_class(current_segment_, decoder_, bin_class_id_); hex_view_->set_data(bin_class); if (!binary_data_exists_) return; if (!save_button_->isEnabled()) save_button_->setEnabled(true); } void View::save_data() const { assert(decoder_); assert(signal_); if (!signal_) return; GlobalSettings settings; const QString dir = settings.value("MainWindow/SaveDirectory").toString(); const QString file_name = QFileDialog::getSaveFileName( parent_, tr("Save Binary Data"), dir, tr("Binary Data Files (*.bin);;All Files (*)")); if (file_name.isEmpty()) return; QFile file(file_name); if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { pair selection = hex_view_->get_selection(); vector data; data.resize(selection.second - selection.first + 1); signal_->get_merged_binary_data_chunks_by_offset(current_segment_, decoder_, bin_class_id_, selection.first, selection.second, &data); int64_t bytes_written = file.write((const char*)data.data(), data.size()); if ((bytes_written == -1) || ((uint64_t)bytes_written != data.size())) { QMessageBox msg(parent_); msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name)); msg.setStandardButtons(QMessageBox::Ok); msg.setIcon(QMessageBox::Warning); msg.exec(); return; } } } void View::save_data_as_hex_dump(bool with_offset, bool with_ascii) const { assert(decoder_); assert(signal_); if (!signal_) return; GlobalSettings settings; const QString dir = settings.value("MainWindow/SaveDirectory").toString(); const QString file_name = QFileDialog::getSaveFileName( parent_, tr("Save Binary Data"), dir, tr("Hex Dumps (*.txt);;All Files (*)")); if (file_name.isEmpty()) return; QFile file(file_name); if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { pair selection = hex_view_->get_selection(); vector data; data.resize(selection.second - selection.first + 1); signal_->get_merged_binary_data_chunks_by_offset(current_segment_, decoder_, bin_class_id_, selection.first, selection.second, &data); QTextStream out_stream(&file); uint64_t offset = selection.first; uint64_t n = hex_view_->get_bytes_per_line(); QString s; while (offset < selection.second) { size_t end = std::min((uint64_t)(selection.second), offset + n); offset = hex_view_->create_hex_line(offset, end, &s, with_offset, with_ascii); out_stream << s << endl; } out_stream << endl; if (out_stream.status() != QTextStream::Ok) { QMessageBox msg(parent_); msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name)); msg.setStandardButtons(QMessageBox::Ok); msg.setIcon(QMessageBox::Warning); msg.exec(); return; } } } void View::on_selected_decoder_changed(int index) { if (signal_) disconnect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int))); reset_data(); decoder_ = (Decoder*)decoder_selector_->itemData(index).value(); // Find the signal that contains the selected decoder for (const shared_ptr& ds : decode_signals_) for (const shared_ptr& dec : ds->decoder_stack()) if (decoder_ == dec.get()) signal_ = ds.get(); class_selector_->clear(); if (signal_) { // Populate binary class selector uint32_t bin_classes = decoder_->get_binary_class_count(); for (uint32_t i = 0; i < bin_classes; i++) { const data::decode::DecodeBinaryClassInfo* class_info = decoder_->get_binary_class(i); class_selector_->addItem(class_info->description, QVariant::fromValue(i)); } connect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int)), this, SLOT(on_new_binary_data(unsigned int, void*, unsigned int))); } update_data(); } void View::on_selected_class_changed(int index) { bin_class_id_ = class_selector_->itemData(index).value(); binary_data_exists_ = signal_->get_binary_data_chunk_count(current_segment_, decoder_, bin_class_id_); update_data(); } void View::on_signal_name_changed(const QString &name) { (void)name; SignalBase* sb = qobject_cast(QObject::sender()); assert(sb); DecodeSignal* signal = dynamic_cast(sb); assert(signal); // Update all decoder entries provided by this signal auto stack = signal->decoder_stack(); if (stack.size() > 1) { for (const shared_ptr& dec : stack) { QString title = QString("%1 (%2)").arg(signal->name(), dec->name()); int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get())); if (index != -1) decoder_selector_->setItemText(index, title); } } else if (!stack.empty()) { shared_ptr& dec = stack.at(0); int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get())); if (index != -1) decoder_selector_->setItemText(index, signal->name()); } } void View::on_new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id) { if ((segment_id == current_segment_) && (decoder == decoder_) && (bin_class_id == bin_class_id_)) if (!delayed_view_updater_.isActive()) delayed_view_updater_.start(); } void View::on_decoder_stacked(void* decoder) { // TODO This doesn't change existing entries for the same signal - but it should as the naming scheme may change Decoder* d = static_cast(decoder); // Only add the decoder if it has binary output if (d->get_binary_class_count() == 0) return; // Find the signal that contains the selected decoder DecodeSignal* signal = nullptr; for (const shared_ptr& ds : decode_signals_) for (const shared_ptr& dec : ds->decoder_stack()) if (d == dec.get()) signal = ds.get(); assert(signal); // Add the decoder to the list QString title = QString("%1 (%2)").arg(signal->name(), d->name()); decoder_selector_->addItem(title, QVariant::fromValue((void*)d)); } void View::on_decoder_removed(void* decoder) { Decoder* d = static_cast(decoder); // Remove the decoder from the list int index = decoder_selector_->findData(QVariant::fromValue((void*)d)); if (index != -1) decoder_selector_->removeItem(index); } void View::on_actionSave_triggered(QAction* action) { int save_type = SaveTypeBinary; if (action) save_type = action->data().toInt(); switch (save_type) { case SaveTypeBinary: save_data(); break; case SaveTypeHexDumpPlain: save_data_as_hex_dump(false, false); break; case SaveTypeHexDumpWithOffset: save_data_as_hex_dump(true, false); break; case SaveTypeHexDumpComplete: save_data_as_hex_dump(true, true); break; } } void View::perform_delayed_view_update() { if (signal_ && !binary_data_exists_) if (signal_->get_binary_data_chunk_count(current_segment_, decoder_, bin_class_id_)) binary_data_exists_ = true; update_data(); } } // namespace decoder_binary } // namespace views } // namespace pv pulseview-0.4.2/pv/views/decoder_binary/view.hpp000600 001750 001750 00000006142 13640725356 021426 0ustar00uweuwe000000 000000 /* * This file is part of the PulseView project. * * Copyright (C) 2019 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_DECODERBINARY_VIEW_HPP #define PULSEVIEW_PV_VIEWS_DECODERBINARY_VIEW_HPP #include #include #include #include #include #include #include "QHexView.hpp" namespace pv { class Session; namespace views { namespace decoder_binary { // When adding an entry here, don't forget to update SaveTypeNames as well enum SaveType { SaveTypeBinary, SaveTypeHexDumpPlain, SaveTypeHexDumpWithOffset, SaveTypeHexDumpComplete, SaveTypeCount // Indicates how many save types there are, must always be last }; extern const char* SaveTypeNames[SaveTypeCount]; class View : public ViewBase { Q_OBJECT public: explicit View(Session &session, bool is_main_view=false, QMainWindow *parent = nullptr); virtual ViewType get_type() const; /** * 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(); virtual void clear_decode_signals(); virtual void add_decode_signal(shared_ptr signal); virtual void remove_decode_signal(shared_ptr signal); virtual void save_settings(QSettings &settings) const; virtual void restore_settings(QSettings &settings); private: void reset_data(); void update_data(); void save_data() const; void save_data_as_hex_dump(bool with_offset=false, bool with_ascii=false) const; private Q_SLOTS: void on_selected_decoder_changed(int index); void on_selected_class_changed(int index); void on_signal_name_changed(const QString &name); void on_new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id); void on_decoder_stacked(void* decoder); void on_decoder_removed(void* decoder); void on_actionSave_triggered(QAction* action = nullptr); virtual void perform_delayed_view_update(); private: QWidget* parent_; QComboBox *decoder_selector_, *format_selector_, *class_selector_; QStackedWidget *stacked_widget_; QHexView *hex_view_; QToolButton* save_button_; QAction* save_action_; data::DecodeSignal *signal_; const data::decode::Decoder *decoder_; uint32_t bin_class_id_; bool binary_data_exists_; }; } // namespace decoder_binary } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_DECODERBINARY_VIEW_HPP pulseview-0.4.2/pv/views/viewbase.hpp000600 001750 001750 00000007031 13640725356 017306 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 #include #ifdef ENABLE_DECODE #include #endif using std::shared_ptr; using std::vector; namespace pv { class Session; namespace view { class DecodeTrace; class Signal; } namespace views { // When adding an entry here, don't forget to update ViewTypeNames as well enum ViewType { ViewTypeTrace, #ifdef ENABLE_DECODE ViewTypeDecoderBinary, #endif ViewTypeCount // Indicates how many view types there are, must always be last }; extern const char* ViewTypeNames[ViewTypeCount]; class ViewBase : public QWidget { Q_OBJECT public: static const int MaxViewAutoUpdateRate; public: explicit ViewBase(Session &session, bool is_main_view = false, QMainWindow *parent = nullptr); virtual ViewType get_type() const = 0; bool is_main_view() const; /** * 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. */ vector< 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::TimeUnit time_unit_; vector< shared_ptr > signalbases_; #ifdef ENABLE_DECODE vector< shared_ptr > decode_signals_; #endif /// 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.2/pv/views/trace/000700 001750 001750 00000000000 13640725776 016071 5ustar00uweuwe000000 000000 pulseview-0.4.2/pv/views/trace/signal.hpp000600 001750 001750 00000006124 13640725356 020056 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 #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 std::map save_settings() const; virtual void restore_settings(QSettings &settings); virtual void restore_settings(std::map 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.2/pv/views/trace/trace.hpp000600 001750 001750 00000013246 13640725356 017702 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.2/pv/views/trace/standardbar.cpp000600 001750 001750 00000022403 13640725356 021057 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_->center_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.2/pv/views/trace/tracepalette.hpp000600 001750 001750 00000002301 13640725356 021247 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.2/pv/views/trace/viewitemowner.cpp000600 001750 001750 00000002645 13640725356 021504 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.2/pv/views/trace/header.hpp000600 001750 001750 00000004603 13640725356 020031 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.2/pv/views/trace/tracetreeitemowner.cpp000600 001750 001750 00000007306 13640725356 022507 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 center 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.2/pv/views/trace/decodetrace.cpp000600 001750 001750 00000154277 13640725356 021053 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 #include #include #include "decodetrace.hpp" #include "view.hpp" #include "viewport.hpp" #include #include #include #include #include #include #include #include #include #include #include using std::abs; using std::find_if; using std::lock_guard; using std::make_pair; using std::max; using std::min; using std::numeric_limits; using std::pair; using std::shared_ptr; using std::tie; using std::vector; using pv::data::decode::Annotation; using pv::data::decode::AnnotationClass; using pv::data::decode::Row; using pv::data::decode::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 QColor DecodeTrace::ExpandMarkerWarnColor = QColor(0xFF, 0xA5, 0x00); // QColorConstants::Svg::orange const QColor DecodeTrace::ExpandMarkerHiddenColor = QColor(0x69, 0x69, 0x69); // QColorConstants::Svg::dimgray const uint8_t DecodeTrace::ExpansionAreaHeaderAlpha = 10 * 255 / 100; const uint8_t DecodeTrace::ExpansionAreaAlpha = 5 * 255 / 100; const int DecodeTrace::ArrowSize = 6; const double DecodeTrace::EndCapWidth = 5; const int DecodeTrace::RowTitleMargin = 7; const int DecodeTrace::DrawPadding = 100; const int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz const int DecodeTrace::AnimationDurationInTicks = 7; const int DecodeTrace::HiddenRowHideDelay = 1000; // 1 second /** * Helper function for forceUpdate() */ void invalidateLayout(QLayout* layout) { // Recompute the given layout and all its child layouts recursively for (int i = 0; i < layout->count(); i++) { QLayoutItem *item = layout->itemAt(i); if (item->layout()) invalidateLayout(item->layout()); else item->invalidate(); } layout->invalidate(); layout->activate(); } void forceUpdate(QWidget* widget) { // Update all child widgets recursively for (QObject* child : widget->children()) if (child->isWidgetType()) forceUpdate((QWidget*)child); // Invalidate the layout of the widget itself if (widget->layout()) invalidateLayout(widget->layout()); } ContainerWidget::ContainerWidget(QWidget *parent) : QWidget(parent) { } void ContainerWidget::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); widgetResized(this); } DecodeTrace::DecodeTrace(pv::Session &session, shared_ptr signalbase, int index) : Trace(signalbase), session_(session), show_hidden_rows_(false), delete_mapper_(this), show_hide_mapper_(this), row_show_hide_mapper_(this) { decode_signal_ = dynamic_pointer_cast(base_); GlobalSettings settings; always_show_all_rows_ = settings.value(GlobalSettings::Key_Dec_AlwaysShowAllRows).toBool(); GlobalSettings::add_change_handler(this); // 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 default_row_height_ = (ViewItemPaintParams::text_height() * 6) / 4; annotation_height_ = (ViewItemPaintParams::text_height() * 5) / 4; // 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(&row_show_hide_mapper_, SIGNAL(mapped(int)), this, SLOT(on_show_hide_row(int))); connect(&class_show_hide_mapper_, SIGNAL(mapped(QWidget*)), this, SLOT(on_show_hide_class(QWidget*))); connect(&delayed_trace_updater_, SIGNAL(timeout()), this, SLOT(on_delayed_trace_update())); delayed_trace_updater_.setSingleShot(true); delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate); connect(&animation_timer_, SIGNAL(timeout()), this, SLOT(on_animation_timer())); animation_timer_.setInterval(1000 / 50); connect(&delayed_hidden_row_hider_, SIGNAL(timeout()), this, SLOT(on_hide_hidden_rows())); delayed_hidden_row_hider_.setSingleShot(true); delayed_hidden_row_hider_.setInterval(HiddenRowHideDelay); default_marker_shape_ << QPoint(0, -ArrowSize); default_marker_shape_ << QPoint(ArrowSize, 0); default_marker_shape_ << QPoint(0, ArrowSize); } DecodeTrace::~DecodeTrace() { GlobalSettings::remove_change_handler(this); for (DecodeTraceRow& r : rows_) { for (QCheckBox* cb : r.selectors) delete cb; delete r.selector_container; delete r.header_container; delete r.container; } } bool DecodeTrace::enabled() const { return true; } shared_ptr DecodeTrace::base() const { return base_; } void DecodeTrace::set_owner(TraceTreeItemOwner *owner) { Trace::set_owner(owner); // The owner is set in trace::View::signals_changed(), which is a slot. // So after this trace was added to the view, we won't have an owner // that we need to initialize in update_rows(). Once we do, we call it // from on_decode_reset(). on_decode_reset(); } pair DecodeTrace::v_extents() const { // Make an empty decode trace appear symmetrical if (visible_rows_ == 0) return make_pair(-default_row_height_, default_row_height_); unsigned int height = 0; for (const DecodeTraceRow& r : rows_) if (r.currently_visible) height += r.height; return make_pair(-default_row_height_, height); } 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) { lock_guard lock(row_modification_mutex_); unsigned int visible_rows; #if DECODETRACE_SHOW_RENDER_TIME render_time_.restart(); #endif // Set default pen to allow for text width calculation p.setPen(Qt::black); 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)); visible_rows = 0; int y = get_visual_y(); for (DecodeTraceRow& r : rows_) { // If the row is hidden, we don't want to fetch annotations assert(r.decode_row); assert(r.decode_row->decoder()); if ((!r.decode_row->decoder()->visible()) || ((!r.decode_row->visible() && (!show_hidden_rows_) && (!r.expanding) && (!r.expanded) && (!r.collapsing)))) { r.currently_visible = false; continue; } deque annotations; decode_signal_->get_annotation_subset(annotations, r.decode_row, current_segment_, sample_range.first, sample_range.second); // Show row if there are visible annotations, when user wants to see // all rows that have annotations somewhere and this one is one of them // or when the row has at least one hidden annotation class r.currently_visible = !annotations.empty(); if (!r.currently_visible) { size_t ann_count = decode_signal_->get_annotation_count(r.decode_row, current_segment_); r.currently_visible = ((always_show_all_rows_ || r.has_hidden_classes) && (ann_count > 0)) || r.expanded; } if (r.currently_visible) { draw_annotations(annotations, p, pp, y, r); y += r.height; visible_rows++; } } draw_unresolved_period(p, pp.left(), pp.right()); if (visible_rows != visible_rows_) { visible_rows_ = visible_rows; // 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); #if DECODETRACE_SHOW_RENDER_TIME qDebug() << "Rendering" << base_->name() << "took" << render_time_.elapsed() << "ms"; #endif } void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp) { unsigned int y = get_visual_y(); update_expanded_rows(); for (const DecodeTraceRow& r : rows_) { if (!r.currently_visible) continue; p.setPen(QPen(Qt::NoPen)); if (r.expand_marker_highlighted) p.setBrush(QApplication::palette().brush(QPalette::Highlight)); else if (!r.decode_row->visible()) p.setBrush(ExpandMarkerHiddenColor); else if (r.has_hidden_classes) p.setBrush(ExpandMarkerWarnColor); else p.setBrush(QApplication::palette().brush(QPalette::WindowText)); // Draw expansion marker QPolygon marker(r.expand_marker_shape); marker.translate(pp.left(), y); p.drawPolygon(marker); p.setBrush(QApplication::palette().brush(QPalette::WindowText)); const QRect text_rect(pp.left() + ArrowSize * 2, y - r.height / 2, pp.right() - pp.left(), r.height); const QString h(r.decode_row->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(text_rect.translated(dx, dy), f, h); // Draw the text if (!r.decode_row->visible()) p.setPen(ExpandMarkerHiddenColor); else p.setPen(QApplication::palette().color(QPalette::WindowText)); p.drawText(text_rect, f, h); y += r.height; } if (show_hover_marker_) paint_hover_marker(p); } void DecodeTrace::update_stack_button() { const vector< shared_ptr > &stack = decode_signal_->decoder_stack(); // Only show decoders in the menu that can be stacked onto the last one in the stack if (!stack.empty()) { const srd_decoder* d = stack.back()->get_srd_decoder(); if (d->outputs) { pv::widgets::DecoderMenu *const decoder_menu = new pv::widgets::DecoderMenu(stack_button_, (const char*)(d->outputs->data)); connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)), this, SLOT(on_stack_decoder(srd_decoder*))); decoder_menu->setStyleSheet("QMenu { menu-scrollable: 1; }"); stack_button_->setMenu(decoder_menu); stack_button_->show(); return; } } // No decoders available for stacking stack_button_->setMenu(nullptr); stack_button_->hide(); } void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form) { 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 stack_button_ = new QPushButton(tr("Stack Decoder"), parent); stack_button_->setToolTip(tr("Stack a higher-level decoder on top of this one")); update_stack_button(); 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(); } selected_row_ = nullptr; const DecodeTraceRow* r = get_row_at_point(click_pos); if (r) selected_row_ = r->decode_row; const View *const view = owner_->view(); assert(view); QPoint pos = view->viewport()->mapFrom(parent, click_pos); // Default sample range is "from here" const pair sample_range = get_view_sample_range(pos.x(), 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); } QAction *const copy_annotation_to_clipboard = new QAction(tr("Copy annotation text to clipboard"), this); copy_annotation_to_clipboard->setIcon(QIcon::fromTheme("edit-paste", QIcon(":/icons/edit-paste.svg"))); connect(copy_annotation_to_clipboard, SIGNAL(triggered()), this, SLOT(on_copy_annotation_to_clipboard())); menu->addAction(copy_annotation_to_clipboard); 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); if (!view->cursors()->enabled()) { export_all_rows_with_cursor->setEnabled(false); export_row_with_cursor->setEnabled(false); } return menu; } void DecodeTrace::delete_pressed() { on_delete(); } void DecodeTrace::hover_point_changed(const QPoint &hp) { Trace::hover_point_changed(hp); assert(owner_); DecodeTraceRow* hover_row = get_row_at_point(hp); // Row expansion marker handling for (DecodeTraceRow& r : rows_) r.expand_marker_highlighted = false; if (hover_row) { int row_y = get_row_y(hover_row); if ((hp.x() > 0) && (hp.x() < (int)(ArrowSize + 3 + hover_row->title_width)) && (hp.y() > (int)(row_y - ArrowSize)) && (hp.y() < (int)(row_y + ArrowSize))) { hover_row->expand_marker_highlighted = true; show_hidden_rows_ = true; delayed_hidden_row_hider_.start(); } } // Tooltip handling if (hp.x() > 0) { QString ann = get_annotation_at_point(hp); if (!ann.isEmpty()) { 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 = default_row_height_ + 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_row_y(hover_row) - default_row_height_ - text_size.height() - padding); const View *const view = owner_->view(); assert(view); QToolTip::showText(view->viewport()->mapToGlobal(p), ann); } else QToolTip::hideText(); } else QToolTip::hideText(); } void DecodeTrace::mouse_left_press_event(const QMouseEvent* event) { // Update container widths which depend on the scrollarea's current width update_expanded_rows(); // Handle row expansion marker for (DecodeTraceRow& r : rows_) { if (!r.expand_marker_highlighted) continue; unsigned int y = get_row_y(&r); if ((event->x() > 0) && (event->x() <= (int)(ArrowSize + 3 + r.title_width)) && (event->y() > (int)(y - (default_row_height_ / 2))) && (event->y() <= (int)(y + (default_row_height_ / 2)))) { if (r.expanded) { r.collapsing = true; r.expanded = false; r.anim_shape = ArrowSize; } else { r.expanding = true; r.anim_shape = 0; // Force geometry update of the widget container to get // an up-to-date height (which also depends on the width) forceUpdate(r.container); r.container->setVisible(true); r.expanded_height = 2 * default_row_height_ + r.container->sizeHint().height(); } r.animation_step = 0; r.anim_height = r.height; animation_timer_.start(); } } } void DecodeTrace::draw_annotations(deque& annotations, QPainter &p, const ViewItemPaintParams &pp, int y, const DecodeTraceRow& row) { 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(); // 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, pp, y, row); else if (block_ann_count > 0) draw_annotation_block(block_start, prev_end, block_class, block_class_uniform, p, y, row); block_ann_count = 0; } if (a_is_separate) { draw_annotation(a, p, pp, y, row); // 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_id(); block_class_uniform = true; } else if (a->ann_class_id() != block_class) block_class_uniform = false; block_ann_count++; } } if (block_ann_count == 1) draw_annotation(prev_ann, p, pp, y, row); else if (block_ann_count > 0) draw_annotation_block(block_start, prev_end, block_class, block_class_uniform, p, y, row); } void DecodeTrace::draw_annotation(const Annotation* a, QPainter &p, const ViewItemPaintParams &pp, int y, const DecodeTraceRow& row) 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; p.setPen(row.ann_class_dark_color.at(a->ann_class_id())); p.setBrush(row.ann_class_color.at(a->ann_class_id())); if ((start > (pp.right() + DrawPadding)) || (end < (pp.left() - DrawPadding))) return; if (a->start_sample() == a->end_sample()) draw_instant(a, p, start, y); else draw_range(a, p, 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 y, const DecodeTraceRow& row) const { const double top = y + .5 - annotation_height_ / 2; const double bottom = y + .5 + annotation_height_ / 2; const double width = end - start; // 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) { p.setPen(row.ann_class_dark_color.at(ann_class)); p.setBrush(QBrush(row.ann_class_color.at(ann_class), Qt::Dense4Pattern)); } else { p.setPen(QColor(Qt::darkGray)); p.setBrush(QBrush(Qt::gray, Qt::Dense4Pattern)); } if (width <= 1) p.drawLine(QPointF(start, top), QPointF(start, bottom)); else { const QRectF rect(start, top, width, bottom - top); const int r = annotation_height_ / 4; p.drawRoundedRect(rect, r, r); } } void DecodeTrace::draw_instant(const Annotation* a, QPainter &p, 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) + annotation_height_; const QRectF rect(x - w / 2, y - annotation_height_ / 2, w, annotation_height_); p.drawRoundedRect(rect, annotation_height_ / 2, annotation_height_ / 2); p.setPen(Qt::black); p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text); } void DecodeTrace::draw_range(const Annotation* a, QPainter &p, qreal start, qreal end, int y, const ViewItemPaintParams &pp, int row_title_width) const { const qreal top = y + .5 - annotation_height_ / 2; const qreal bottom = y + .5 + annotation_height_ / 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() + ArrowSize + 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 - annotation_height_ / 2, real_width, annotation_height_); 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 &s : *annotations) { const int w = p.boundingRect(QRectF(), 0, s).width(); if (w <= rect.width() && w > best_width) best_annotation = s, 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 left, int right) const { 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 - (annotation_height_ / 2) - 0.5, end - start, annotation_height_); 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; } unsigned int DecodeTrace::get_row_y(const DecodeTraceRow* row) const { assert(row); unsigned int y = get_visual_y(); for (const DecodeTraceRow& r : rows_) { if (!r.currently_visible) continue; if (row->decode_row == r.decode_row) break; else y += r.height; } return y; } DecodeTraceRow* DecodeTrace::get_row_at_point(const QPoint &point) { int y = get_visual_y() - (default_row_height_ / 2); for (DecodeTraceRow& r : rows_) { if (!r.currently_visible) continue; if ((point.y() >= y) && (point.y() < (int)(y + r.height))) return &r; y += r.height; } return nullptr; } const QString DecodeTrace::get_annotation_at_point(const QPoint &point) { if (!enabled()) return QString(); const pair sample_range = get_view_sample_range(point.x(), point.x() + 1); const DecodeTraceRow* r = get_row_at_point(point); if (!r) return QString(); if (point.y() > (int)(get_row_y(r) + (annotation_height_ / 2))) return QString(); deque annotations; decode_signal_->get_annotation_subset(annotations, r->decode_row, current_segment_, sample_range.first, sample_range.second); return (annotations.empty()) ? QString() : annotations[0]->annotations()->front(); } void DecodeTrace::create_decoder_form(int index, shared_ptr &dec, QWidget *parent, QFormLayout *form) { GlobalSettings settings; assert(dec); const srd_decoder *const decoder = dec->get_srd_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->visible()); 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(deque& annotations) const { 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"); const bool has_sample_range = format.contains("%s"); const bool has_row_name = format.contains("%r"); const bool has_dec_name = format.contains("%d"); const bool has_class_name = format.contains("%c"); const bool has_first_ann_text = format.contains("%1"); const bool has_all_ann_text = format.contains("%a"); QFile file(file_name); if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { QTextStream out_stream(&file); for (const Annotation* ann : annotations) { QString out_text = format; if (has_sample_range) { const QString sample_range = QString("%1-%2") \ .arg(QString::number(ann->start_sample()), QString::number(ann->end_sample())); out_text = out_text.replace("%s", sample_range); } if (has_dec_name) out_text = out_text.replace("%d", quote + QString::fromUtf8(ann->row()->decoder()->name()) + quote); if (has_row_name) { const QString row_name = quote + ann->row()->description() + quote; out_text = out_text.replace("%r", row_name); } if (has_class_name) { const QString class_name = quote + ann->ann_class_name() + quote; out_text = out_text.replace("%c", class_name); } if (has_first_ann_text) { const QString first_ann_text = quote + ann->annotations()->front() + quote; out_text = out_text.replace("%1", first_ann_text); } if (has_all_ann_text) { QString all_ann_text; for (const QString &s : *(ann->annotations())) all_ann_text = all_ann_text + quote + s + quote + ","; all_ann_text.chop(1); 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") + "\n\n" + tr("File %1 could not be written to.").arg(file_name)); msg.setStandardButtons(QMessageBox::Ok); msg.setIcon(QMessageBox::Warning); msg.exec(); } void DecodeTrace::initialize_row_widgets(DecodeTraceRow* r, unsigned int row_id) { // Set colors and fixed widths QFontMetrics m(QApplication::font()); QPalette header_palette = owner_->view()->palette(); QPalette selector_palette = owner_->view()->palette(); if (GlobalSettings::current_theme_is_dark()) { header_palette.setColor(QPalette::Background, QColor(255, 255, 255, ExpansionAreaHeaderAlpha)); selector_palette.setColor(QPalette::Background, QColor(255, 255, 255, ExpansionAreaAlpha)); } else { header_palette.setColor(QPalette::Background, QColor(0, 0, 0, ExpansionAreaHeaderAlpha)); selector_palette.setColor(QPalette::Background, QColor(0, 0, 0, ExpansionAreaAlpha)); } const int w = m.boundingRect(r->decode_row->title()).width() + RowTitleMargin; r->title_width = w; // Set up top-level container connect(r->container, SIGNAL(widgetResized(QWidget*)), this, SLOT(on_row_container_resized(QWidget*))); QVBoxLayout* vlayout = new QVBoxLayout(); r->container->setLayout(vlayout); // Add header container vlayout->addWidget(r->header_container); vlayout->setContentsMargins(0, 0, 0, 0); vlayout->setSpacing(0); QHBoxLayout* header_container_layout = new QHBoxLayout(); r->header_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); r->header_container->setMinimumSize(0, default_row_height_); r->header_container->setLayout(header_container_layout); r->header_container->layout()->setContentsMargins(10, 2, 10, 2); r->header_container->setAutoFillBackground(true); r->header_container->setPalette(header_palette); // Add widgets inside the header container QCheckBox* cb = new QCheckBox(); r->row_visibility_checkbox = cb; header_container_layout->addWidget(cb); cb->setText(tr("Show this row")); cb->setChecked(r->decode_row->visible()); row_show_hide_mapper_.setMapping(cb, row_id); connect(cb, SIGNAL(stateChanged(int)), &row_show_hide_mapper_, SLOT(map())); QPushButton* btn = new QPushButton(); header_container_layout->addWidget(btn); btn->setFlat(true); btn->setStyleSheet(":hover { background-color: palette(button); color: palette(button-text); border:0; }"); btn->setText(tr("Show All")); btn->setProperty("decode_trace_row_ptr", QVariant::fromValue((void*)r)); connect(btn, SIGNAL(clicked(bool)), this, SLOT(on_show_all_classes())); btn = new QPushButton(); header_container_layout->addWidget(btn); btn->setFlat(true); btn->setStyleSheet(":hover { background-color: palette(button); color: palette(button-text); border:0; }"); btn->setText(tr("Hide All")); btn->setProperty("decode_trace_row_ptr", QVariant::fromValue((void*)r)); connect(btn, SIGNAL(clicked(bool)), this, SLOT(on_hide_all_classes())); header_container_layout->addStretch(); // To left-align the header widgets // Add selector container vlayout->addWidget(r->selector_container); r->selector_container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); r->selector_container->setLayout(new FlowLayout(r->selector_container)); r->selector_container->setAutoFillBackground(true); r->selector_container->setPalette(selector_palette); // Add all classes that can be toggled vector ann_classes = r->decode_row->ann_classes(); for (const AnnotationClass* ann_class : ann_classes) { cb = new QCheckBox(); cb->setText(tr(ann_class->description)); cb->setChecked(ann_class->visible); int dim = ViewItemPaintParams::text_height() - 2; QPixmap pixmap(dim, dim); pixmap.fill(r->ann_class_color[ann_class->id]); cb->setIcon(pixmap); r->selector_container->layout()->addWidget(cb); r->selectors.push_back(cb); cb->setProperty("ann_class_ptr", QVariant::fromValue((void*)ann_class)); cb->setProperty("decode_trace_row_ptr", QVariant::fromValue((void*)r)); class_show_hide_mapper_.setMapping(cb, cb); connect(cb, SIGNAL(stateChanged(int)), &class_show_hide_mapper_, SLOT(map())); } } void DecodeTrace::update_rows() { if (!owner_) return; lock_guard lock(row_modification_mutex_); for (DecodeTraceRow& r : rows_) r.exists = false; unsigned int row_id = 0; for (Row* decode_row : decode_signal_->get_rows()) { // Find row in our list auto r_it = find_if(rows_.begin(), rows_.end(), [&](DecodeTraceRow& r){ return r.decode_row == decode_row; }); DecodeTraceRow* r = nullptr; if (r_it == rows_.end()) { // Row doesn't exist yet, create and append it DecodeTraceRow nr; nr.decode_row = decode_row; nr.height = default_row_height_; nr.expanded_height = default_row_height_; nr.currently_visible = false; nr.has_hidden_classes = decode_row->has_hidden_classes(); nr.expand_marker_highlighted = false; nr.expanding = false; nr.expanded = false; nr.collapsing = false; nr.expand_marker_shape = default_marker_shape_; nr.container = new ContainerWidget(owner_->view()->scrollarea()); nr.header_container = new QWidget(nr.container); nr.selector_container = new QWidget(nr.container); nr.row_color = get_row_color(decode_row->index()); vector ann_classes = decode_row->ann_classes(); for (const AnnotationClass* ann_class : ann_classes) { nr.ann_class_color[ann_class->id] = get_annotation_color(nr.row_color, ann_class->id); nr.ann_class_dark_color[ann_class->id] = nr.ann_class_color[ann_class->id].darker(); } rows_.push_back(nr); r = &rows_.back(); initialize_row_widgets(r, row_id); } else r = &(*r_it); r->exists = true; row_id++; } // If there's only one row, it must not be hidden or else it can't be un-hidden if (row_id == 1) rows_.front().row_visibility_checkbox->setEnabled(false); // Remove any rows that no longer exist, obeying that iterators are invalidated bool any_exists; do { any_exists = false; for (unsigned int i = 0; i < rows_.size(); i++) if (!rows_[i].exists) { delete rows_[i].row_visibility_checkbox; for (QCheckBox* cb : rows_[i].selectors) delete cb; delete rows_[i].selector_container; delete rows_[i].header_container; delete rows_[i].container; rows_.erase(rows_.begin() + i); any_exists = true; break; } } while (any_exists); } void DecodeTrace::set_row_expanded(DecodeTraceRow* r) { r->height = r->expanded_height; r->expanding = false; r->expanded = true; // For details on this, see on_animation_timer() r->expand_marker_shape.setPoint(0, 0, 0); r->expand_marker_shape.setPoint(1, ArrowSize, ArrowSize); r->expand_marker_shape.setPoint(2, 2*ArrowSize, 0); r->container->resize(owner_->view()->viewport()->width() - r->container->pos().x(), r->height - 2 * default_row_height_); } void DecodeTrace::set_row_collapsed(DecodeTraceRow* r) { r->height = default_row_height_; r->collapsing = false; r->expanded = false; r->expand_marker_shape = default_marker_shape_; r->container->setVisible(false); r->container->resize(owner_->view()->viewport()->width() - r->container->pos().x(), r->height - 2 * default_row_height_); } void DecodeTrace::update_expanded_rows() { for (DecodeTraceRow& r : rows_) { if (r.expanding || r.expanded) r.expanded_height = 2 * default_row_height_ + r.container->sizeHint().height(); if (r.expanded) r.height = r.expanded_height; int x = 2 * ArrowSize; int y = get_row_y(&r) + default_row_height_; // Only update the position if it actually changes if ((x != r.container->pos().x()) || (y != r.container->pos().y())) r.container->move(x, y); int w = owner_->view()->viewport()->width() - x; int h = r.height - 2 * default_row_height_; // Only update the dimension if they actually change if ((w != r.container->sizeHint().width()) || (h != r.container->sizeHint().height())) r.container->resize(w, h); } } void DecodeTrace::on_setting_changed(const QString &key, const QVariant &value) { Trace::on_setting_changed(key, value); if (key == GlobalSettings::Key_Dec_AlwaysShowAllRows) always_show_all_rows_ = value.toBool(); } 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() { update_rows(); 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::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); update_rows(); create_popup_form(); } void DecodeTrace::on_delete_decoder(int index) { decode_signal_->remove_decoder(index); update_rows(); owner_->extents_changed(false, true); 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) owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_show_hide_row(int row_id) { if (row_id >= (int)rows_.size()) return; rows_[row_id].decode_row->set_visible(!rows_[row_id].decode_row->visible()); if (!rows_[row_id].decode_row->visible()) set_row_collapsed(&rows_[row_id]); owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_show_hide_class(QWidget* sender) { void* ann_class_ptr = sender->property("ann_class_ptr").value(); assert(ann_class_ptr); AnnotationClass* ann_class = (AnnotationClass*)ann_class_ptr; ann_class->visible = !ann_class->visible; void* row_ptr = sender->property("decode_trace_row_ptr").value(); assert(row_ptr); DecodeTraceRow* row = (DecodeTraceRow*)row_ptr; row->has_hidden_classes = row->decode_row->has_hidden_classes(); owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_show_all_classes() { void* row_ptr = QObject::sender()->property("decode_trace_row_ptr").value(); assert(row_ptr); DecodeTraceRow* row = (DecodeTraceRow*)row_ptr; for (QCheckBox* cb : row->selectors) cb->setChecked(true); row->has_hidden_classes = false; owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_hide_all_classes() { void* row_ptr = QObject::sender()->property("decode_trace_row_ptr").value(); assert(row_ptr); DecodeTraceRow* row = (DecodeTraceRow*)row_ptr; for (QCheckBox* cb : row->selectors) cb->setChecked(false); row->has_hidden_classes = true; owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_row_container_resized(QWidget* sender) { sender->update(); owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_copy_annotation_to_clipboard() { if (!selected_row_) return; deque annotations; decode_signal_->get_annotation_subset(annotations, selected_row_, current_segment_, selected_sample_range_.first, selected_sample_range_.first); if (annotations.empty()) return; QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(annotations.front()->annotations()->front(), QClipboard::Clipboard); if (clipboard->supportsSelection()) clipboard->setText(annotations.front()->annotations()->front(), QClipboard::Selection); } 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() { if (!selected_row_) return; deque annotations; 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); } void DecodeTrace::on_export_all_rows_from_here() { deque annotations; decode_signal_->get_annotation_subset(annotations, current_segment_, selected_sample_range_.first, selected_sample_range_.second); if (!annotations.empty()) export_annotations(annotations); } void DecodeTrace::on_animation_timer() { bool animation_finished = true; for (DecodeTraceRow& r : rows_) { if (!(r.expanding || r.collapsing)) continue; unsigned int height_delta = r.expanded_height - default_row_height_; if (r.expanding) { if (r.height < r.expanded_height) { r.anim_height += height_delta / (float)AnimationDurationInTicks; r.height = min((int)r.anim_height, (int)r.expanded_height); r.anim_shape += ArrowSize / (float)AnimationDurationInTicks; animation_finished = false; } else set_row_expanded(&r); } if (r.collapsing) { if (r.height > default_row_height_) { r.anim_height -= height_delta / (float)AnimationDurationInTicks; r.height = max((int)r.anim_height, (int)0); r.anim_shape -= ArrowSize / (float)AnimationDurationInTicks; animation_finished = false; } else set_row_collapsed(&r); } // The expansion marker shape switches between // 0/-A, A/0, 0/A (default state; anim_shape=0) and // 0/ 0, A/A, 2A/0 (expanded state; anim_shape=ArrowSize) r.expand_marker_shape.setPoint(0, 0, -ArrowSize + r.anim_shape); r.expand_marker_shape.setPoint(1, ArrowSize, r.anim_shape); r.expand_marker_shape.setPoint(2, 2*r.anim_shape, ArrowSize - r.anim_shape); } if (animation_finished) animation_timer_.stop(); owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } void DecodeTrace::on_hide_hidden_rows() { // Make all hidden traces invisible again unless the user is hovering over a row name bool any_highlighted = false; for (DecodeTraceRow& r : rows_) if (r.expand_marker_highlighted) any_highlighted = true; if (!any_highlighted) { show_hidden_rows_ = false; owner_->extents_changed(false, true); owner_->row_item_appearance_changed(false, true); } } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.2/pv/views/trace/logicsignal.hpp000600 001750 001750 00000010677 13640725356 021104 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 std::map save_settings() const; virtual void restore_settings(std::map 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.2/pv/views/trace/viewwidget.hpp000600 001750 001750 00000010023 13640725356 020750 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 #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 keyPressEvent(QKeyEvent *event); void keyReleaseEvent(QKeyEvent *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_; pv::util::Timestamp mouse_down_offset_; shared_ptr mouse_down_item_; /// Keyboard modifiers that were active when mouse was last moved or clicked Qt::KeyboardModifiers mouse_modifiers_; bool item_dragging_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWWIDGET_HPP pulseview-0.4.2/pv/views/trace/tracetreeitem.cpp000600 001750 001750 00000006365 13640725356 021440 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.2/pv/views/trace/cursor.cpp000600 001750 001750 00000006172 13640725356 020114 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 #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, view_.ruler()->get_ruler_time_from_absolute_time(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); } QMenu *Cursor::create_header_context_menu(QWidget *parent) { QMenu *const menu = new QMenu(parent); QAction *const snap_disable = new QAction(tr("Disable snapping"), this); snap_disable->setCheckable(true); snap_disable->setChecked(snapping_disabled_); connect(snap_disable, &QAction::toggled, this, [=](bool checked){snapping_disabled_ = checked;}); menu->addAction(snap_disable); return menu; } 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.2/pv/views/trace/analogsignal.hpp000600 001750 001750 00000014477 13640725356 021252 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 std::map save_settings() const; virtual void restore_settings(std::map 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_; double signal_min_, signal_max_; // Min/max values of this signal's analog data bool show_analog_minor_grid_; QColor high_fill_color_; bool show_sampling_points_, fill_high_areas_; 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 // --------------------------------------------------------------------------- // Note: Make sure to update .. when adding a trace-configurable variable here 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 DisplayType display_type_; bool autoranging_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_ANALOGSIGNAL_HPP pulseview-0.4.2/pv/views/trace/flag.hpp000600 001750 001750 00000004514 13640725356 017513 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. */ virtual bool enabled() const override; /** * Gets the text to show in the marker. */ virtual QString get_text() const override; /** * Sets the text to show in the marker. */ virtual void set_text(const QString &text) override; virtual pv::widgets::Popup* create_popup(QWidget *parent) override; virtual QMenu* create_header_context_menu(QWidget *parent) override; virtual void delete_pressed() override; QRectF label_rect(const QRectF &rect) const override; 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.2/pv/views/trace/cursorpair.hpp000600 001750 001750 00000006745 13640725356 021003 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; virtual const pv::util::Timestamp time() const override; float get_x() const override; virtual const pv::util::Timestamp delta(const pv::util::Timestamp& other) const override; QPoint drag_point(const QRect &rect) const override; pv::widgets::Popup* create_popup(QWidget *parent) override; QMenu* create_header_context_menu(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(int max_width = 0, std::function query_size = [](const QString& s) -> double { (void)s; return 0; }); 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: QString format_string_sub(int time_precision, int freq_precision, bool show_unit = true); private: shared_ptr first_, second_; QColor fill_color_; QSizeF text_size_; QRectF label_area_; bool label_incomplete_; bool show_interval_, show_frequency_, show_samples_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSORPAIR_HPP pulseview-0.4.2/pv/views/trace/tracegroup.cpp000600 001750 001750 00000010573 13640725356 020752 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.2/pv/views/trace/cursor.hpp000600 001750 001750 00000003641 13640725356 020117 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. */ virtual bool enabled() const override; /** * Gets the text to show in the marker. */ virtual QString get_text() const override; /** * Gets the marker label rectangle. * @param rect The rectangle of the ruler client area. * @return Returns the label rectangle. */ virtual QRectF label_rect(const QRectF &rect) const override; virtual QMenu* create_header_context_menu(QWidget *parent) override; private: shared_ptr get_other_cursor() const; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_CURSOR_HPP pulseview-0.4.2/pv/views/trace/timemarker.cpp000600 001750 001750 00000012154 13640725356 020734 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 "ruler.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) { } const pv::util::Timestamp TimeMarker::time() const { return time_; } void TimeMarker::set_time(const pv::util::Timestamp& time) { time_ = time; if (value_widget_) { QSignalBlocker blocker(value_widget_); value_widget_->setValue(view_.ruler()->get_ruler_time_from_absolute_time(time)); } 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::set_text(const QString &text) { (void)text; } 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(view_.ruler()->get_ruler_time_from_absolute_time(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) { set_time(view_.ruler()->get_absolute_time_from_ruler_time(value)); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.2/pv/views/trace/timeitem.cpp000600 001750 001750 00000003155 13640725356 020412 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) { if (snapping_disabled_) { set_time(view_.offset() + (drag_point_.x() + delta.x() - 0.5) * view_.scale()); } else { 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()); } } const pv::util::Timestamp TimeItem::delta(const pv::util::Timestamp& other) const { return other - time(); } bool TimeItem::is_snapping_disabled() const { return snapping_disabled_; } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.2/pv/views/trace/marginwidget.cpp000600 001750 001750 00000004320 13640725356 021251 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); connect(p, SIGNAL(closed()), this, SLOT(on_popup_closed())); 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(); } ViewWidget::keyPressEvent(event); } void MarginWidget::on_popup_closed() { bool cursor_above_widget = rect().contains(mapFromGlobal(QCursor::pos())); if (!cursor_above_widget) mouse_point_ = QPoint(INT_MIN, INT_MIN); update(); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.2/pv/views/trace/viewitempaintparams.hpp000600 001750 001750 00000004030 13640725356 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 . */ #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.2/pv/views/trace/timemarker.hpp000600 001750 001750 00000007144 13640725356 020744 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. */ virtual const pv::util::Timestamp time() 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; /** * 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; /** * Sets the text to show in the marker. */ virtual void set_text(const QString &text); /** * 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_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_MARKER_HPP pulseview-0.4.2/pv/views/trace/signal.cpp000600 001750 001750 00000010304 13640725356 020044 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 { std::map settings_map = save_settings(); for (auto& entry : settings_map) settings.setValue(entry.first, entry.second); } std::map Signal::save_settings() const { return std::map(); } void Signal::restore_settings(QSettings &settings) { std::map settings_map; QStringList keys = settings.allKeys(); for (int i = 0; i < keys.size(); i++) settings_map[keys.at(i)] = settings.value(keys.at(i)); restore_settings(settings_map); } void Signal::restore_settings(std::map 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.2/pv/views/trace/triggermarker.cpp000600 001750 001750 00000004056 13640725356 021443 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); } const pv::util::Timestamp TriggerMarker::time() const { return time_; } 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.2/pv/views/trace/tracepalette.cpp000600 001750 001750 00000004307 13640725356 021252 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.2/pv/views/trace/viewwidget.cpp000600 001750 001750 00000022005 13640725356 020746 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) { if (event->modifiers() & Qt::ShiftModifier) view_.show_cursors(false); mouse_down_point_ = event->pos(); mouse_down_offset_ = view_.offset() + event->pos().x() * view_.scale(); 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::keyReleaseEvent(QKeyEvent *event) { // Update mouse_modifiers_ also if modifiers change, but pointer doesn't move if ((mouse_point_.x() >= 0) && (mouse_point_.y() >= 0)) // mouse is inside mouse_modifiers_ = event->modifiers(); update(); } void ViewWidget::keyPressEvent(QKeyEvent *event) { // Update mouse_modifiers_ also if modifiers change, but pointer doesn't move if ((mouse_point_.x() >= 0) && (mouse_point_.y() >= 0)) // mouse is inside mouse_modifiers_ = event->modifiers(); update(); } void ViewWidget::mouseMoveEvent(QMouseEvent *event) { assert(event); mouse_point_ = event->pos(); mouse_modifiers_ = event->modifiers(); if (!event->buttons()) item_hover(get_mouse_over_item(event->pos()), event->pos()); if (event->buttons() & Qt::LeftButton) { if (event->modifiers() & Qt::ShiftModifier) { // Cursor drag pv::util::Timestamp current_offset = view_.offset() + event->pos().x() * view_.scale(); const int drag_distance = qAbs(current_offset.convert_to() - mouse_down_offset_.convert_to()) / view_.scale(); if (drag_distance > QApplication::startDragDistance()) { view_.show_cursors(true); view_.set_cursors(mouse_down_offset_, current_offset); } else view_.show_cursors(false); } else { 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_); } } // Force a repaint of the widget to update highlighted parts update(); } void ViewWidget::leaveEvent(QEvent*) { bool cursor_above_widget = rect().contains(mapFromGlobal(QCursor::pos())); // We receive leaveEvent also when the widget loses focus even when // the mouse cursor hasn't moved at all - e.g. when the popup shows. // However, we don't want to reset mouse_position_ when the mouse is // still above this widget as doing so would break the context menu if (!cursor_above_widget) mouse_point_ = QPoint(INT_MIN, INT_MIN); mouse_modifiers_ = Qt::NoModifier; item_hover(nullptr, QPoint()); update(); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.2/pv/views/trace/trace.cpp000600 001750 001750 00000027265 13640725356 017703 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) { bool was_antialiased = p.testRenderHint(QPainter::Antialiasing); p.setRenderHint(QPainter::Antialiasing, false); p.setPen(axis_pen_); p.drawLine(QPointF(pp.left(), y), QPointF(pp.right(), y)); p.setRenderHint(QPainter::Antialiasing, was_antialiased); } 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(); bool was_antialiased = p.testRenderHint(QPainter::Antialiasing); p.setRenderHint(QPainter::Antialiasing, false); p.drawLine(x, get_visual_y() + extents.first, x, get_visual_y() + extents.second); p.setRenderHint(QPainter::Antialiasing, was_antialiased); } 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_->layout()); suicidal->deleteLater(); } // Repopulate the popup widgets::QWidthAdjustingScrollArea* scrollarea = new widgets::QWidthAdjustingScrollArea(); QWidget* scrollarea_content = new QWidget(scrollarea); scrollarea->setWidget(scrollarea_content); scrollarea->setWidgetResizable(true); scrollarea->setContentsMargins(0, 0, 0, 0); scrollarea->setFrameShape(QFrame::NoFrame); scrollarea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollarea_content->setContentsMargins(0, 0, 0, 0); popup_->setLayout(new QVBoxLayout()); popup_->layout()->addWidget(scrollarea); popup_->layout()->setContentsMargins(0, 0, 0, 0); popup_form_ = new QFormLayout(scrollarea_content); popup_form_->setSizeConstraint(QLayout::SetMinAndMaxSize); 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_absolute_time_from_x_pos(p.x())); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.2/pv/views/trace/viewitempaintparams.cpp000600 001750 001750 00000002510 13640725356 022660 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.2/pv/views/trace/viewitem.hpp000600 001750 001750 00000011326 13640725356 020432 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 #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(); virtual void hover_point_changed(const QPoint &hp); /** * Handles left mouse button press events. * @param event the mouse event that triggered this handler. */ virtual void mouse_left_press_event(const QMouseEvent* event); 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.2/pv/views/trace/ruler.hpp000600 001750 001750 00000014442 13640725356 017734 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_absolute_time_from_x_pos(uint32_t x) const; pv::util::Timestamp get_ruler_time_from_x_pos(uint32_t x) const; pv::util::Timestamp get_ruler_time_from_absolute_time(const pv::util::Timestamp& abs_time) const; pv::util::Timestamp get_absolute_time_from_ruler_time(const pv::util::Timestamp& ruler_time) const; shared_ptr get_reference_item() const; protected: virtual void contextMenuEvent(QContextMenuEvent *event) override; void resizeEvent(QResizeEvent*) override; virtual void item_hover(const shared_ptr &item, QPoint pos) 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_resetZeroPosition(); 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_; shared_ptr hover_item_; uint32_t context_menu_x_pos_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_RULER_HPP pulseview-0.4.2/pv/views/trace/view.cpp000600 001750 001750 00000146723 13640725356 017560 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 "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::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; /* Area at the top and bottom of the view that can't be scrolled out of sight */ const int View::ViewScrollMargin = 50; const int View::ScaleUnits[3] = {1, 2, 5}; CustomScrollArea::CustomScrollArea(QWidget *parent) : QAbstractScrollArea(parent) { } 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, QMainWindow *parent) : ViewBase(session, is_main_view, parent), // Note: Place defaults in View::reset_view_state(), not here splitter_(new QSplitter()), header_was_shrunk_(false), // The splitter remains unchanged after a reset, so this goes here sticky_scrolling_(false), // Default setting is set in MainWindow::setup_ui() scroll_needs_defaults_(true) { 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); lazy_event_handler_.setInterval(1000 / ViewBase::MaxViewAutoUpdateRate); // Set up local keyboard shortcuts zoom_in_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Plus), this, SLOT(on_zoom_in_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut); zoom_in_shortcut_->setAutoRepeat(false); zoom_out_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Minus), this, SLOT(on_zoom_out_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut); zoom_out_shortcut_->setAutoRepeat(false); zoom_in_shortcut_2_ = new QShortcut(QKeySequence(Qt::Key_Up), this, SLOT(on_zoom_in_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut); zoom_out_shortcut_2_ = new QShortcut(QKeySequence(Qt::Key_Down), this, SLOT(on_zoom_out_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut); home_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Home), this, SLOT(on_scroll_to_start_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut); home_shortcut_->setAutoRepeat(false); end_shortcut_ = new QShortcut(QKeySequence(Qt::Key_End), this, SLOT(on_scroll_to_end_shortcut_triggered()), nullptr, Qt::WidgetWithChildrenShortcut); end_shortcut_->setAutoRepeat(false); grab_ruler_left_shortcut_ = new QShortcut(QKeySequence(Qt::Key_1), this, nullptr, nullptr, Qt::WidgetWithChildrenShortcut); connect(grab_ruler_left_shortcut_, &QShortcut::activated, this, [=]{on_grab_ruler(1);}); grab_ruler_left_shortcut_->setAutoRepeat(false); grab_ruler_right_shortcut_ = new QShortcut(QKeySequence(Qt::Key_2), this, nullptr, nullptr, Qt::WidgetWithChildrenShortcut); connect(grab_ruler_right_shortcut_, &QShortcut::activated, this, [=]{on_grab_ruler(2);}); grab_ruler_right_shortcut_->setAutoRepeat(false); cancel_grab_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this, nullptr, nullptr, Qt::WidgetWithChildrenShortcut); connect(cancel_grab_shortcut_, &QShortcut::activated, this, [=]{grabbed_widget_ = nullptr;}); cancel_grab_shortcut_->setAutoRepeat(false); // Trigger the initial event manually. The default device has signals // which were created before this object came into being signals_changed(); // Make sure the transparent widgets are on the top ruler_->raise(); header_->raise(); reset_view_state(); } View::~View() { GlobalSettings::remove_change_handler(this); } ViewType View::get_type() const { return ViewTypeTrace; } 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; zero_offset_ = 0; custom_zero_offset_set_ = false; 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; grabbed_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_; } vector< shared_ptr > View::signals() const { return signals_; } shared_ptr View::get_signal_by_signalbase(shared_ptr base) const { shared_ptr ret_val; for (const shared_ptr& s : signals_) if (s->base() == base) { ret_val = s; break; } return ret_val; } void View::clear_signals() { ViewBase::clear_signals(); signals_.clear(); } void View::add_signal(const shared_ptr signal) { ViewBase::add_signalbase(signal->base()); signals_.push_back(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() { ViewBase::clear_decode_signals(); decode_traces_.clear(); } void View::add_decode_signal(shared_ptr signal) { ViewBase::add_decode_signal(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; } ViewBase::remove_decode_signal(signal); } #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_; } QAbstractScrollArea* View::scrollarea() const { return scrollarea_; } 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_); GlobalSettings::store_timestamp(settings, "offset", offset_); if (custom_zero_offset_set_) GlobalSettings::store_timestamp(settings, "zero_offset", -zero_offset_); else settings.remove("zero_offset"); for (const shared_ptr& signal : 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("offset")) { // This also updates ruler_offset_ set_offset(GlobalSettings::restore_timestamp(settings, "offset")); } if (settings.contains("zero_offset")) set_zero_position(GlobalSettings::restore_timestamp(settings, "zero_offset")); if (settings.contains("splitter_state")) splitter_->restoreState(settings.value("splitter_state").toByteArray()); if (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_ + zero_offset_; 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) { zero_offset_ = -position; custom_zero_offset_set_ = true; // Force an immediate update of the offsets set_offset(offset_, true); ruler_->update(); } void View::reset_zero_position() { zero_offset_ = 0; // When enabled, the first trigger for this segment is used as the zero position GlobalSettings settings; bool trigger_is_zero_time = settings.value(GlobalSettings::Key_View_TriggerIsZeroTime).toBool(); if (trigger_is_zero_time) { vector triggers = session_.get_triggers(current_segment_); if (triggers.size() > 0) zero_offset_ = triggers.front(); } custom_zero_offset_set_ = false; // Force an immediate update of the offsets set_offset(offset_, true); ruler_->update(); } pv::util::Timestamp View::zero_offset() const { return zero_offset_; } 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(); } void View::set_h_offset(int offset) { scrollarea_->horizontalScrollBar()->setSliderPosition(offset); header_->update(); viewport_->update(); } int View::get_h_scrollbar_maximum() const { return scrollarea_->horizontalScrollBar()->maximum(); } unsigned int View::depth() const { return 0; } 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)); if (!custom_zero_offset_set_) reset_zero_position(); 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(); } vector< shared_ptr > View::get_visible_data() const { // Make a set of all the visible data objects vector< shared_ptr > visible_data; for (const shared_ptr& sig : signals_) if (sig->enabled()) visible_data.push_back(sig->data()); return visible_data; } pair View::get_time_extents() const { boost::optional left_time, right_time; vector< shared_ptr > data; if (signals_.size() == 0) return make_pair(0, 0); data.push_back(signals_.front()->data()); for (const shared_ptr& d : 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); } bool View::colored_bg() const { return colored_bg_; } bool View::cursors_shown() const { return show_cursors_; } void View::show_cursors(bool show) { if (show_cursors_ != show) { show_cursors_ = show; cursor_state_changed(show); ruler_->update(); viewport_->update(); } } void View::set_cursors(pv::util::Timestamp& first, pv::util::Timestamp& second) { assert(cursors_); cursors_->first()->set_time(first); cursors_->second()->set_time(second); ruler_->update(); viewport_->update(); } void View::center_cursors() { assert(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_; } shared_ptr View::add_flag(const Timestamp& time) { shared_ptr flag = make_shared(*this, time, QString("%1").arg(next_flag_text_)); flags_.push_back(flag); next_flag_text_ = (next_flag_text_ >= 'Z') ? 'A' : (next_flag_text_ + 1); time_item_appearance_changed(true, true); return flag; } 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) { GlobalSettings settings; if (key == GlobalSettings::Key_View_ColoredBG) { colored_bg_ = settings.value(GlobalSettings::Key_View_ColoredBG).toBool(); viewport_->update(); } if ((key == GlobalSettings::Key_View_ShowSamplingPoints) || (key == GlobalSettings::Key_View_ShowAnalogMinorGrid)) viewport_->update(); if (key == GlobalSettings::Key_View_TriggerIsZeroTime) on_settingViewTriggerIsZeroTime_changed(value); if (key == GlobalSettings::Key_View_SnapDistance) 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; if (!custom_zero_offset_set_) reset_zero_position(); 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) { int top_margin = ViewScrollMargin; int btm_margin = ViewScrollMargin; vscrollbar->setRange(extents.first - areaSize.height() + top_margin, extents.second - btm_margin); } if (scroll_needs_defaults_) { set_scroll_default(); 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(); if (grabbed_widget_) { int64_t nearest = get_nearest_level_change(hover_point_); pv::util::Timestamp mouse_time = offset_ + hover_point_.x() * scale_; if (nearest == -1) { grabbed_widget_->set_time(mouse_time); } else { grabbed_widget_->set_time(nearest / get_signal_under_mouse_cursor()->base()->get_samplerate()); } } } else if (type == QEvent::MouseButtonPress) { grabbed_widget_ = nullptr; const QMouseEvent *const mouse_event = (QMouseEvent*)event; if ((object == viewport_) && (mouse_event->button() & Qt::LeftButton)) { // Send event to all trace tree items const vector> trace_tree_items( list_by_type()); for (const shared_ptr& r : trace_tree_items) r->mouse_left_press_event(mouse_event); } } 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); if (!lazy_event_handler_.isActive()) 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::on_zoom_in_shortcut_triggered() { zoom(1); } void View::on_zoom_out_shortcut_triggered() { zoom(-1); } void View::on_scroll_to_start_shortcut_triggered() { set_h_offset(0); } void View::on_scroll_to_end_shortcut_triggered() { set_h_offset(get_h_scrollbar_maximum()); } void View::h_scroll_value_changed(int value) { if (updating_scroll_) 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::on_grab_ruler(int ruler_id) { if (!cursors_shown()) { center_cursors(); show_cursors(); } // Release the grabbed widget if its trigger hotkey was pressed twice if (ruler_id == 1) grabbed_widget_ = (grabbed_widget_ == cursors_->first().get()) ? nullptr : cursors_->first().get(); else grabbed_widget_ = (grabbed_widget_ == cursors_->second().get()) ? nullptr : cursors_->second().get(); if (grabbed_widget_) grabbed_widget_->set_time(ruler_->get_absolute_time_from_x_pos( mapFromGlobal(QCursor::pos()).x() - header_width())); } void View::signals_changed() { using sigrok::Channel; 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(); if (!custom_zero_offset_set_) set_zero_position(0); 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) { (void)new_value; if (!custom_zero_offset_set_) 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.2/pv/views/trace/flag.cpp000600 001750 001750 00000010230 13640725356 017476 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 "ruler.hpp" #include #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 { QString s; const shared_ptr ref_item = view_.ruler()->get_reference_item(); if (!ref_item || (ref_item.get() == this)) s = text_; else s = Ruler::format_time_with_distance( ref_item->time(), ref_item->delta(time_), view_.tick_prefix(), view_.time_unit(), view_.tick_precision()); return s; } void Flag::set_text(const QString &text) { text_ = text; view_.time_item_appearance_changed(true, false); } QRectF Flag::label_rect(const QRectF &rect) const { QRectF r; const shared_ptr ref_item = view_.ruler()->get_reference_item(); if (!ref_item || (ref_item.get() == this)) { r = TimeMarker::label_rect(rect); } else { // TODO: Remove code duplication between here and cursor.cpp 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 height = label_size.height(); const float top = rect.height() - label_size.height() - TimeMarker::ArrowSize - 0.5f; const pv::util::Timestamp& delta = ref_item->delta(time_); if (delta >= 0) r = QRectF(x, top, label_size.width(), height); else r = QRectF(x - label_size.width(), top, label_size.width(), height); } return r; } 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); QAction *const snap_disable = new QAction(tr("Disable snapping"), this); snap_disable->setCheckable(true); snap_disable->setChecked(snapping_disabled_); connect(snap_disable, &QAction::toggled, this, [=](bool checked){snapping_disabled_ = checked;}); menu->addAction(snap_disable); 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) { set_text(text); } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.2/pv/views/trace/header.cpp000600 001750 001750 00000013316 13640725356 020025 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.2/pv/views/trace/tracetreeitemowner.hpp000600 001750 001750 00000005206 13640725356 022511 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.2/pv/views/trace/marginwidget.hpp000600 001750 001750 00000003644 13640725356 021266 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 Q_SLOTS: virtual void contextMenuEvent(QContextMenuEvent *event); virtual void keyPressEvent(QKeyEvent *event); virtual void on_popup_closed(); }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_MARGINWIDGET_HPP pulseview-0.4.2/pv/views/trace/timeitem.hpp000600 001750 001750 00000004014 13640725356 020412 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); bool snapping_disabled_ = false; public: /** * Sets the time of the marker. */ virtual void set_time(const pv::util::Timestamp& time) = 0; /** * Returns the time this time item is set to. * @return 0 in case there is no valid time (e.g. for a cursor pair) */ virtual const pv::util::Timestamp time() const = 0; virtual float get_x() const = 0; virtual const pv::util::Timestamp delta(const pv::util::Timestamp& other) 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); bool is_snapping_disabled() const; protected: View &view_; }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_TIMEITEM_HPP pulseview-0.4.2/pv/views/trace/logicsignal.cpp000600 001750 001750 00000047352 13640725356 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(); } std::map LogicSignal::save_settings() const { std::map result; result["trace_height"] = signal_height_; return result; } void LogicSignal::restore_settings(std::map settings) { auto entry = settings.find("trace_height"); if (entry != settings.end()) { const int old_height = signal_height_; signal_height_ = settings["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.2/pv/views/trace/tracegroup.hpp000600 001750 001750 00000006352 13640725356 020757 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.2/pv/views/trace/viewitemowner.hpp000600 001750 001750 00000004533 13640725356 021507 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.2/pv/views/trace/triggermarker.hpp000600 001750 001750 00000004621 13640725356 021446 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; virtual const pv::util::Timestamp time() const 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.2/pv/views/trace/viewitemiterator.hpp000600 001750 001750 00000006646 13640725356 022215 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.2/pv/views/trace/view.hpp000600 001750 001750 00000034456 13640725356 017564 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 #include "cursorpair.hpp" #include "flag.hpp" #include "trace.hpp" #include "tracetreeitemowner.hpp" using std::list; using std::unordered_map; 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 ViewScrollMargin; static const int ScaleUnits[3]; public: explicit View(Session &session, bool is_main_view=false, QMainWindow *parent = nullptr); ~View(); virtual ViewType get_type() const; /** * 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(); // This method is needed for TraceTreeItemOwner, not ViewBase const Session& session() const; // This method is needed for TraceTreeItemOwner, not ViewBase /** * Returns the signals contained in this view. */ vector< shared_ptr > signals() const; shared_ptr get_signal_by_signalbase(shared_ptr base) 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; QAbstractScrollArea* scrollarea() 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(); pv::util::Timestamp zero_offset() const; /** * Returns the vertical scroll offset. */ int owner_visual_v_offset() const; /** * Sets the visual v-offset. */ void set_v_offset(int offset); /** * Sets the visual h-offset. */ void set_h_offset(int offset); /** * Gets the length of the horizontal scrollbar. */ int get_h_scrollbar_maximum() const; /** * 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); vector< shared_ptr > get_visible_data() const; pair get_time_extents() const; /** * Returns true if the trace background should be drawn with a colored background. */ bool colored_bg() const; /** * Returns true if cursors are displayed, false otherwise. */ bool cursors_shown() const; /** * Shows or hides the cursors. */ void show_cursors(bool show = true); /** * Sets the cursors to the given offsets. * You still have to call show_cursors() separately. */ void set_cursors(pv::util::Timestamp& first, pv::util::Timestamp& second); /** * Moves the cursors to a convenient position in the view. * You still have to call show_cursors() separately. */ void center_cursors(); /** * Returns a reference to the pair of cursors. */ shared_ptr cursors() const; /** * Adds a new flag at a specified time. */ shared_ptr 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 on_zoom_in_shortcut_triggered(); void on_zoom_out_shortcut_triggered(); void on_scroll_to_start_shortcut_triggered(); void on_scroll_to_end_shortcut_triggered(); void h_scroll_value_changed(int value); void v_scroll_value_changed(); void on_grab_ruler(int ruler_id); 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_; QShortcut *zoom_in_shortcut_, *zoom_in_shortcut_2_; QShortcut *zoom_out_shortcut_, *zoom_out_shortcut_2_; QShortcut *home_shortcut_, *end_shortcut_; QShortcut *grab_ruler_left_shortcut_, *grab_ruler_right_shortcut_; QShortcut *cancel_grab_shortcut_; vector< 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_; /// The offset of the zero point in seconds. pv::util::Timestamp zero_offset_; /// Shows whether the user set a custom zero offset that we should keep bool custom_zero_offset_set_; 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_; TimeMarker* grabbed_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.2/pv/views/trace/tracetreeitem.hpp000600 001750 001750 00000006121 13640725356 021433 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 "viewitem.hpp" using std::enable_shared_from_this; using std::pair; namespace pv { namespace views { namespace trace { class TraceTreeItemOwner; class TraceTreeItem : public ViewItem, 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. */ virtual 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.2/pv/views/trace/ruler.cpp000600 001750 001750 00000027250 13640725356 017730 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 "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); QString unit_string; if (unit == pv::util::TimeUnit::Time) unit_string = "s"; // Note: In case of pv::util::TimeUnit::None, unit_string remains empty // 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, unit_string, sign); else return pv::util::format_time_minutes(t, precision, sign); } pv::util::Timestamp Ruler::get_absolute_time_from_x_pos(uint32_t x) const { return view_.offset() + ((double)x + 0.5) * view_.scale(); } pv::util::Timestamp Ruler::get_ruler_time_from_x_pos(uint32_t x) const { return view_.ruler_offset() + ((double)x + 0.5) * view_.scale(); } pv::util::Timestamp Ruler::get_ruler_time_from_absolute_time(const pv::util::Timestamp& abs_time) const { return abs_time + view_.zero_offset(); } pv::util::Timestamp Ruler::get_absolute_time_from_ruler_time(const pv::util::Timestamp& ruler_time) const { return ruler_time - view_.zero_offset(); } 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); if (view_.zero_offset().convert_to() != 0) { QAction *const reset_zero_position = new QAction(tr("Reset zero point"), this); connect(reset_zero_position, SIGNAL(triggered()), this, SLOT(on_resetZeroPosition())); menu->addAction(reset_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()); } void Ruler::item_hover(const shared_ptr &item, QPoint pos) { (void)pos; hover_item_ = dynamic_pointer_cast(item); } shared_ptr Ruler::get_reference_item() const { // Note: time() returns 0 if item returns no valid time if (mouse_modifiers_ & Qt::ShiftModifier) return nullptr; if (hover_item_ && (hover_item_->time() != 0)) return hover_item_; shared_ptr ref_item; const vector< shared_ptr > items(view_.time_items()); for (auto i = items.rbegin(); i != items.rend(); i++) { if ((*i)->enabled() && (*i)->selected()) { if (!ref_item) ref_item = *i; else { // Return nothing if multiple items are selected ref_item.reset(); break; } } } if (ref_item && (ref_item->time() == 0)) ref_item.reset(); return ref_item; } 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) { hover_item_ = view_.add_flag(get_absolute_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() { hover_item_ = view_.add_flag(get_absolute_time_from_x_pos(mouse_down_point_.x())); } void Ruler::on_setZeroPosition() { view_.set_zero_position(get_absolute_time_from_x_pos(mouse_down_point_.x())); } void Ruler::on_resetZeroPosition() { view_.reset_zero_position(); } 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.2/pv/views/trace/cursorpair.cpp000600 001750 001750 00000023610 13640725356 020764 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 "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)), label_incomplete_(true) { GlobalSettings::add_change_handler(this); GlobalSettings settings; fill_color_ = QColor::fromRgba(settings.value( GlobalSettings::Key_View_CursorFillColor).value()); show_frequency_ = settings.value( GlobalSettings::Key_View_CursorShowFrequency).value(); show_interval_ = settings.value( GlobalSettings::Key_View_CursorShowInterval).value(); show_samples_ = settings.value( GlobalSettings::Key_View_CursorShowSamples).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); } const pv::util::Timestamp CursorPair::time() const { return 0; } float CursorPair::get_x() const { return (first_->get_x() + second_->get_x()) / 2.0f; } const pv::util::Timestamp CursorPair::delta(const pv::util::Timestamp& other) const { if (other < second_->time()) return other - first_->time(); else return other - second_->time(); } 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; } QMenu *CursorPair::create_header_context_menu(QWidget *parent) { QMenu *menu = new QMenu(parent); QAction *displayIntervalAction = new QAction(tr("Display interval"), this); displayIntervalAction->setCheckable(true); displayIntervalAction->setChecked(show_interval_); menu->addAction(displayIntervalAction); connect(displayIntervalAction, &QAction::toggled, displayIntervalAction, [=]{ GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_CursorShowInterval, !settings.value(GlobalSettings::Key_View_CursorShowInterval).value()); }); QAction *displayFrequencyAction = new QAction(tr("Display frequency"), this); displayFrequencyAction->setCheckable(true); displayFrequencyAction->setChecked(show_frequency_); menu->addAction(displayFrequencyAction); connect(displayFrequencyAction, &QAction::toggled, displayFrequencyAction, [=]{ GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_CursorShowFrequency, !settings.value(GlobalSettings::Key_View_CursorShowFrequency).value()); }); QAction *displaySamplesAction = new QAction(tr("Display samples"), this); displaySamplesAction->setCheckable(true); displaySamplesAction->setChecked(show_samples_); menu->addAction(displaySamplesAction); connect(displaySamplesAction, &QAction::toggled, displaySamplesAction, [=]{ GlobalSettings settings; settings.setValue(GlobalSettings::Key_View_CursorShowSamples, !settings.value(GlobalSettings::Key_View_CursorShowSamples).value()); }); return menu; } 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); 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)); QString text = format_string(text_rect.width(), [&p](const QString& s) -> double { return p.boundingRect(QRectF(), 0, s).width(); }); text_size_ = p.boundingRect(QRectF(), 0, text).size(); 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(int max_width, std::function query_size) { int time_precision = 12; int freq_precision = 12; QString s = format_string_sub(time_precision, freq_precision); // Try full "{time} s / {freq} Hz" format if ((max_width <= 0) || (query_size(s) <= max_width)) { label_incomplete_ = false; return s; } label_incomplete_ = true; // Gradually reduce time precision to match frequency precision while (time_precision > freq_precision) { time_precision--; s = format_string_sub(time_precision, freq_precision); if (query_size(s) <= max_width) return s; } // Gradually reduce both precisions down to zero while (time_precision > 0) { time_precision--; freq_precision--; s = format_string_sub(time_precision, freq_precision); if (query_size(s) <= max_width) return s; } // Try no trailing digits and drop the unit to at least display something s = format_string_sub(0, 0, false); if (query_size(s) <= max_width) return s; // Give up return "..."; } 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()); if (key == GlobalSettings::Key_View_CursorShowFrequency) show_frequency_ = value.value(); if (key == GlobalSettings::Key_View_CursorShowInterval) show_interval_ = value.value(); if (key == GlobalSettings::Key_View_CursorShowSamples) show_samples_ = 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 } QString CursorPair::format_string_sub(int time_precision, int freq_precision, bool show_unit) { QString s = " "; const pv::util::SIPrefix prefix = view_.tick_prefix(); const pv::util::Timestamp diff = abs(second_->time() - first_->time()); const QString time = Ruler::format_time_with_distance( diff, diff, prefix, (show_unit ? view_.time_unit() : pv::util::TimeUnit::None), time_precision, false); // We can only show a frequency when there's a time base if (view_.time_unit() == pv::util::TimeUnit::Time) { int items = 0; if (show_frequency_) { const QString freq = util::format_value_si( 1 / diff.convert_to(), pv::util::SIPrefix::unspecified, freq_precision, (show_unit ? "Hz" : nullptr), false); s = QString("%1").arg(freq); items++; } if (show_interval_) { if (items > 0) s = QString("%1 / %2").arg(s, time); else s = QString("%1").arg(time); items++; } if (show_samples_) { const QString samples = QString::number( (diff * view_.session().get_samplerate()).convert_to()); if (items > 0) s = QString("%1 / %2").arg(s, samples); else s = QString("%1").arg(samples); } } else // In this case, we return the number of samples, really s = time; return s; } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.2/pv/views/trace/analogsignal.cpp000600 001750 001750 00000121761 13640725356 021240 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; // 0.01 units/div const int AnalogSignal::MaxScaleIndex = 10; // 1000 units/div const int AnalogSignal::InfoTextMarginRight = 20; const int AnalogSignal::InfoTextMarginBottom = 5; AnalogSignal::AnalogSignal( pv::Session &session, shared_ptr base) : Signal(session, base), value_at_hover_pos_(std::numeric_limits::quiet_NaN()), scale_index_(4), // 20 per div pos_vdivs_(1), neg_vdivs_(1), resolution_(0), display_type_(DisplayBoth), autoranging_(true) { 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(); } std::map AnalogSignal::save_settings() const { std::map result; result["pos_vdivs"] = pos_vdivs_; result["neg_vdivs"] = neg_vdivs_; result["scale_index"] = scale_index_; result["display_type"] = display_type_; result["autoranging"] = pos_vdivs_; result["div_height"] = div_height_; return result; } void AnalogSignal::restore_settings(std::map settings) { auto entry = settings.find("pos_vdivs"); if (entry != settings.end()) pos_vdivs_ = settings["pos_vdivs"].toInt(); entry = settings.find("neg_vdivs"); if (entry != settings.end()) neg_vdivs_ = settings["neg_vdivs"].toInt(); entry = settings.find("scale_index"); if (entry != settings.end()) { scale_index_ = settings["scale_index"].toInt(); update_scale(); } entry = settings.find("display_type"); if (entry != settings.end()) display_type_ = (DisplayType)(settings["display_type"].toInt()); entry = settings.find("autoranging"); if (entry != settings.end()) autoranging_ = settings["autoranging"].toBool(); entry = settings.find("div_height"); if (entry != settings.end()) { const int old_height = div_height_; div_height_ = settings["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, 2, "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) { bool was_antialiased = p.testRenderHint(QPainter::Antialiasing); 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, was_antialiased); } 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; // QPainter::drawPolyline() is slow, let's paint the lines ourselves for (int64_t i = 1; i < points_count; i++) p.drawLine(points[i - 1], points[i]); 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; double signal_min_ = 0, signal_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 == signal_min_) && (max == signal_max_) && !force_update) return; signal_min_ = min; signal_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 { if ((size_t)hp.x() < value_at_pixel_pos_.size()) value_at_hover_pos_ = value_at_pixel_pos_.at(hp.x()); else 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.2/pv/views/trace/viewitem.cpp000600 001750 001750 00000006031 13640725356 020422 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; } void ViewItem::hover_point_changed(const QPoint &hp) { (void)hp; } void ViewItem::mouse_left_press_event(const QMouseEvent* event) { (void)event; } } // namespace trace } // namespace views } // namespace pv pulseview-0.4.2/pv/views/trace/viewport.cpp000600 001750 001750 00000015053 13640725356 020454 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 #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::SizeHorCursor : Qt::SizeVerCursor); 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; } if (event->device()->type() == QTouchDevice::TouchPad) { 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); // Disable antialiasing for high-DPI displays bool use_antialiasing = window()->windowHandle()->screen()->devicePixelRatio() < 2.0; p.setRenderHint(QPainter::Antialiasing, use_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.2/pv/views/trace/decodetrace.hpp000600 001750 001750 00000022232 13640725356 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 #include "trace.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DECODETRACE_SHOW_RENDER_TIME 0 using std::deque; using std::list; using std::map; using std::mutex; using std::pair; using std::shared_ptr; using std::vector; using pv::data::SignalBase; using pv::data::decode::Annotation; using pv::data::decode::Decoder; using pv::data::decode::Row; struct srd_channel; struct srd_decoder; namespace pv { class Session; namespace data { struct DecodeChannel; class DecodeSignal; namespace decode { class Decoder; class Row; } } // namespace data namespace widgets { class DecoderGroupBox; } namespace views { namespace trace { class ContainerWidget; struct DecodeTraceRow { // When adding a field, make sure it's initialized properly in // DecodeTrace::update_rows() Row* decode_row; unsigned int height, expanded_height, title_width, animation_step; bool exists, currently_visible, has_hidden_classes; bool expand_marker_highlighted, expanding, expanded, collapsing; QPolygon expand_marker_shape; float anim_height, anim_shape; ContainerWidget* container; QWidget* header_container; QWidget* selector_container; QCheckBox* row_visibility_checkbox; vector selectors; QColor row_color; map ann_class_color; map ann_class_dark_color; }; class ContainerWidget : public QWidget { Q_OBJECT public: ContainerWidget(QWidget *parent = nullptr); virtual void resizeEvent(QResizeEvent* event); Q_SIGNALS: void widgetResized(QWidget* sender); }; class DecodeTrace : public Trace { Q_OBJECT private: static const QColor ErrorBgColor; static const QColor NoDecodeColor; static const QColor ExpandMarkerWarnColor; static const QColor ExpandMarkerHiddenColor; static const uint8_t ExpansionAreaHeaderAlpha; static const uint8_t ExpansionAreaAlpha; static const int ArrowSize; static const double EndCapWidth; static const int RowTitleMargin; static const int DrawPadding; static const int MaxTraceUpdateRate; static const int AnimationDurationInTicks; static const int HiddenRowHideDelay; public: DecodeTrace(pv::Session &session, shared_ptr signalbase, int index); ~DecodeTrace(); bool enabled() const; shared_ptr base() const; /** * Sets the owner this trace in the view trace hierachy. * @param The new owner of the trace. */ virtual void set_owner(TraceTreeItemOwner *owner); /** * 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); virtual void delete_pressed(); virtual void hover_point_changed(const QPoint &hp); virtual void mouse_left_press_event(const QMouseEvent* event); private: void draw_annotations(deque& annotations, QPainter &p, const ViewItemPaintParams &pp, int y, const DecodeTraceRow& row); void draw_annotation(const Annotation* a, QPainter &p, const ViewItemPaintParams &pp, int y, const DecodeTraceRow& row) const; void draw_annotation_block(qreal start, qreal end, Annotation::Class ann_class, bool use_ann_format, QPainter &p, int y, const DecodeTraceRow& row) const; void draw_instant(const Annotation* a, QPainter &p, qreal x, int y) const; void draw_range(const Annotation* a, QPainter &p, 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 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; unsigned int get_row_y(const DecodeTraceRow* row) const; DecodeTraceRow* get_row_at_point(const QPoint &point); const QString get_annotation_at_point(const QPoint &point); void update_stack_button(); void create_decoder_form(int index, shared_ptr &dec, QWidget *parent, QFormLayout *form); QComboBox* create_channel_selector(QWidget *parent, const data::decode::DecodeChannel *ch); QComboBox* create_channel_selector_init_state(QWidget *parent, const data::decode::DecodeChannel *ch); void export_annotations(deque& annotations) const; void initialize_row_widgets(DecodeTraceRow* r, unsigned int row_id); void update_rows(); /** * Sets row r to expanded state without forcing an update of the view */ void set_row_expanded(DecodeTraceRow* r); /** * Sets row r to collapsed state without forcing an update of the view */ void set_row_collapsed(DecodeTraceRow* r); void update_expanded_rows(); private Q_SLOTS: void on_setting_changed(const QString &key, const QVariant &value); 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_show_hide_row(int row_id); void on_show_hide_class(QWidget* sender); void on_show_all_classes(); void on_hide_all_classes(); void on_row_container_resized(QWidget* sender); void on_copy_annotation_to_clipboard(); 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(); void on_animation_timer(); void on_hide_hidden_rows(); private: pv::Session &session_; shared_ptr decode_signal_; deque rows_; mutable mutex row_modification_mutex_; map channel_id_map_; // channel selector -> decode channel ID map init_state_map_; // init state selector -> decode channel ID list< shared_ptr > bindings_; const Row* selected_row_; pair selected_sample_range_; vector decoder_forms_; QPushButton* stack_button_; unsigned int default_row_height_, annotation_height_; unsigned int visible_rows_; int min_useful_label_width_; bool always_show_all_rows_, show_hidden_rows_; QSignalMapper delete_mapper_, show_hide_mapper_; QSignalMapper row_show_hide_mapper_, class_show_hide_mapper_; QTimer delayed_trace_updater_, animation_timer_, delayed_hidden_row_hider_; QPolygon default_marker_shape_; #if DECODETRACE_SHOW_RENDER_TIME QElapsedTimer render_time_; #endif }; } // namespace trace } // namespace views } // namespace pv #endif // PULSEVIEW_PV_VIEWS_TRACEVIEW_DECODETRACE_HPP pulseview-0.4.2/pv/views/trace/viewport.hpp000600 001750 001750 00000005251 13640725356 020460 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.2/pv/views/trace/standardbar.hpp000600 001750 001750 00000005525 13640725356 021072 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.2/pv/devicemanager.cpp000600 001750 001750 00000024234 13640725356 017135 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 devices that driver %1 can access...") .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.2/pv/application.cpp000600 001750 001750 00000022362 13640725356 016646 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 #include #include #include #include #include #include #include #ifdef ENABLE_STACKTRACE #include #endif #ifdef ENABLE_DECODE #include #endif #include "application.hpp" #include "config.h" #include "globalsettings.hpp" 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"); } QStringList Application::get_languages() { QStringList files = QDir(":/l10n/").entryList(QStringList("*.qm"), QDir::Files); QStringList result; result << "en"; // Add default language to the set // Remove file extensions for (const QString& file : files) result << file.split(".").front(); result.sort(Qt::CaseInsensitive); return result; } void Application::switch_language(const QString& language) { removeTranslator(&app_translator_); removeTranslator(&qt_translator_); removeTranslator(&qtbase_translator_); if ((language != "C") && (language != "en")) { // Application translations QString resource = ":/l10n/" + language +".qm"; if (app_translator_.load(resource)) installTranslator(&app_translator_); else qWarning() << "Translation resource" << resource << "not found"; // Qt translations QString tr_path(QLibraryInfo::location(QLibraryInfo::TranslationsPath)); if (qt_translator_.load("qt_" + language, tr_path)) installTranslator(&qt_translator_); else qWarning() << "QT translations for" << language << "not found at" << tr_path << ", Qt translations package is probably missing"; // Qt base translations if (qtbase_translator_.load("qtbase_" + language, tr_path)) installTranslator(&qtbase_translator_); else qWarning() << "QT base translations for" << language << "not found at" << tr_path << ", Qt translations package is probably missing"; } if (!topLevelWidgets().empty()) { // Force all windows to update for (QWidget *widget : topLevelWidgets()) widget->update(); QMessageBox msg(topLevelWidgets().front()); msg.setText(tr("Some parts of the application may still " \ "use the previous language. Re-opening the affected windows or " \ "restarting the application will remedy this.")); msg.setStandardButtons(QMessageBox::Ok); msg.setIcon(QMessageBox::Information); msg.exec(); } } void Application::on_setting_changed(const QString &key, const QVariant &value) { if (key == pv::GlobalSettings::Key_General_Language) switch_language(value.toString()); } 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.2/pv/dialogs/000700 001750 001750 00000000000 13640725776 015260 5ustar00uweuwe000000 000000 pulseview-0.4.2/pv/dialogs/settings.cpp000600 001750 001750 00000066045 13640725356 017633 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; QCheckBox *cb; 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); // Generate language combobox QComboBox *language_cb = new QComboBox(); Application* a = qobject_cast(QApplication::instance()); QString current_language = settings.value(GlobalSettings::Key_General_Language).toString(); for (const QString& language : a->get_languages()) { QLocale locale = QLocale(language); QString desc = locale.languageToString(locale.language()); language_cb->addItem(desc, language); if (language == current_language) { int index = language_cb->findText(desc, Qt::MatchFixedString); language_cb->setCurrentIndex(index); } } connect(language_cb, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(on_general_language_changed(const QString&))); general_layout->addRow(tr("User interface language"), language_cb); // Theme combobox 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(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); // Style combobox 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, nullptr)); 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); // Misc cb = create_checkbox(GlobalSettings::Key_General_SaveWithSetup, SLOT(on_general_save_with_setup_changed(int))); general_layout->addRow(tr("Save session &setup along with .sr file"), cb); 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 markers 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); cb = create_checkbox(GlobalSettings::Key_Dec_AlwaysShowAllRows, SLOT(on_dec_alwaysshowallrows_changed(int))); decoder_layout->addRow(tr("Always show all &rows, even if no annotation is visible"), 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; %r: row name; %c: class name")); description_1->setAlignment(Qt::AlignRight); decoder_layout->addRow(description_1); QLabel *description_2 = new QLabel(tr("%1: longest annotation text; %a: all annotation texts; %q: use quotation marks")); 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_language_changed(const QString &text) { GlobalSettings settings; Application* a = qobject_cast(QApplication::instance()); for (const QString& language : a->get_languages()) { QLocale locale = QLocale(language); QString desc = locale.languageToString(locale.language()); if (text == desc) settings.setValue(GlobalSettings::Key_General_Language, language); } } void Settings::on_general_theme_changed(int value) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_General_Theme, value); 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 value) { GlobalSettings settings; if (value == 0) settings.setValue(GlobalSettings::Key_General_Style, ""); else settings.setValue(GlobalSettings::Key_General_Style, QStyleFactory::keys().at(value - 1)); settings.apply_theme(); } void Settings::on_general_save_with_setup_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_General_SaveWithSetup, state ? true : false); } 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); } void Settings::on_dec_alwaysshowallrows_changed(int state) { GlobalSettings settings; settings.setValue(GlobalSettings::Key_Dec_AlwaysShowAllRows, state ? true : false); } #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") + "\n\n" + 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") + "\n\n" + 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.2/pv/dialogs/connect.cpp000600 001750 001750 00000021336 13640725356 017416 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_config_ = new QWidget(); QHBoxLayout *serial_config_layout = new QHBoxLayout(serial_config_); serial_devices_.setEditable(true); serial_devices_.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); serial_baudrate_.setEditable(true); serial_baudrate_.addItem(""); serial_baudrate_.addItem("921600"); serial_baudrate_.addItem("115200"); serial_baudrate_.addItem("57600"); serial_baudrate_.addItem("19200"); serial_baudrate_.addItem("9600"); serial_config_layout->addWidget(&serial_devices_); serial_config_layout->addWidget(&serial_baudrate_); serial_config_layout->addWidget(new QLabel("baud")); serial_config_->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_config_); 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); serial_baudrate_.setEnabled(checked); serial_config_->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_config_->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()); // Set baud rate if specified if (serial_baudrate_.currentText().length() > 0) drvopts[ConfigKey::SERIALCOMM] = Variant::create( QString("%1/8n1").arg(serial_baudrate_.currentText()).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.2/pv/dialogs/storeprogress.cpp000600 001750 001750 00000006263 13640725356 020710 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.") + "\n\n" + 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.2/pv/dialogs/inputoutputoptions.cpp000600 001750 001750 00000003356 13640725356 022023 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.2/pv/dialogs/settings.hpp000600 001750 001750 00000006416 13640725356 017634 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_language_changed(const QString &text); void on_general_theme_changed(int value); void on_general_style_changed(int value); void on_general_save_with_setup_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); void on_dec_alwaysshowallrows_changed(int state); #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.2/pv/dialogs/inputoutputoptions.hpp000600 001750 001750 00000003536 13640725356 022030 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.2/pv/dialogs/storeprogress.hpp000600 001750 001750 00000003226 13640725356 020711 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.2/pv/dialogs/connect.hpp000600 001750 001750 00000004421 13640725356 017417 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_; QWidget *serial_config_; QComboBox serial_devices_, serial_baudrate_; 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.2/pv/data/000700 001750 001750 00000000000 13640725776 014547 5ustar00uweuwe000000 000000 pulseview-0.4.2/pv/data/signalbase.cpp000600 001750 001750 00000052220 13640725356 017360 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_) && (!internal_name_.isEmpty())) 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.2/pv/data/decodesignal.cpp000600 001750 001750 00000123052 13640725356 017673 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 #include "logic.hpp" #include "logicsegment.hpp" #include "decodesignal.hpp" #include "signaldata.hpp" #include #include #include #include using std::lock_guard; using std::make_shared; using std::min; using std::out_of_range; using std::shared_ptr; using std::unique_lock; using pv::data::decode::AnnotationClass; using pv::data::decode::DecodeChannel; 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, bool restart_decode) { assert(decoder); // Set name if this decoder is the first in the list or the name is unchanged const srd_decoder* prev_dec = stack_.empty() ? nullptr : stack_.back()->get_srd_decoder(); const QString prev_dec_name = prev_dec ? QString::fromUtf8(prev_dec->name) : QString(); if ((stack_.empty()) || ((stack_.size() > 0) && (name() == prev_dec_name))) set_name(QString::fromUtf8(decoder->name)); const shared_ptr dec = make_shared(decoder); stack_.push_back(dec); // Include the newly created decode channels in the channel lists update_channel_list(); stack_config_changed_ = true; auto_assign_signals(dec); commit_decoder_channels(); decoder_stacked((void*)dec.get()); if (restart_decode) 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()); decoder_removed(iter->get()); // 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->visible(); dec->set_visible(state); } return state; } void DecodeSignal::reset_decode(bool shutting_down) { resume_decode(); // Make sure the decode thread isn't blocked by pausing 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(); } 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 (decode::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; } // 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 (decode::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 (decode::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(), [](decode::DecodeChannel ch) { return ch.assigned_signal; }); } void DecodeSignal::set_initial_pin_state(const uint16_t channel_id, const int init_state) { for (decode::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 decode::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; if (segment_id >= segments_.size()) return result; if (include_processing) result = segments_[segment_id].samples_decoded_incl; else result = segments_[segment_id].samples_decoded_excl; return result; } vector DecodeSignal::get_rows(bool visible_only) { vector rows; for (const shared_ptr& dec : stack_) { assert(dec); if (visible_only && !dec->visible()) continue; for (Row* row : dec->get_rows()) rows.push_back(row); } return rows; } vector DecodeSignal::get_rows(bool visible_only) const { vector rows; for (const shared_ptr& dec : stack_) { assert(dec); if (visible_only && !dec->visible()) continue; for (const Row* row : dec->get_rows()) rows.push_back(row); } return rows; } uint64_t DecodeSignal::get_annotation_count(const Row* row, uint32_t segment_id) const { if (segment_id >= segments_.size()) return 0; const DecodeSegment* segment = &(segments_.at(segment_id)); auto row_it = segment->annotation_rows.find(row); const RowData* rd; if (row_it == segment->annotation_rows.end()) return 0; else rd = &(row_it->second); return rd->get_annotation_count(); } void DecodeSignal::get_annotation_subset(deque &dest, const Row* row, uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const { lock_guard lock(output_mutex_); if (segment_id >= segments_.size()) return; const DecodeSegment* segment = &(segments_.at(segment_id)); auto row_it = segment->annotation_rows.find(row); const RowData* rd; if (row_it == segment->annotation_rows.end()) return; else rd = &(row_it->second); rd->get_annotation_subset(dest, start_sample, end_sample); } void DecodeSignal::get_annotation_subset(deque &dest, uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const { for (const Row* row : get_rows()) get_annotation_subset(dest, row, segment_id, start_sample, end_sample); } uint32_t DecodeSignal::get_binary_data_chunk_count(uint32_t segment_id, const Decoder* dec, uint32_t bin_class_id) const { if (segments_.size() == 0) return 0; try { const DecodeSegment *segment = &(segments_.at(segment_id)); for (const DecodeBinaryClass& bc : segment->binary_classes) if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id)) return bc.chunks.size(); } catch (out_of_range&) { // Do nothing } return 0; } void DecodeSignal::get_binary_data_chunk(uint32_t segment_id, const Decoder* dec, uint32_t bin_class_id, uint32_t chunk_id, const vector **dest, uint64_t *size) { try { const DecodeSegment *segment = &(segments_.at(segment_id)); for (const DecodeBinaryClass& bc : segment->binary_classes) if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id)) { if (dest) *dest = &(bc.chunks.at(chunk_id).data); if (size) *size = bc.chunks.at(chunk_id).data.size(); return; } } catch (out_of_range&) { // Do nothing } } void DecodeSignal::get_merged_binary_data_chunks_by_sample(uint32_t segment_id, const Decoder* dec, uint32_t bin_class_id, uint64_t start_sample, uint64_t end_sample, vector *dest) const { assert(dest != nullptr); try { const DecodeSegment *segment = &(segments_.at(segment_id)); const DecodeBinaryClass* bin_class = nullptr; for (const DecodeBinaryClass& bc : segment->binary_classes) if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id)) bin_class = &bc; // Determine overall size before copying to resize dest vector only once uint64_t size = 0; uint64_t matches = 0; for (const DecodeBinaryDataChunk& chunk : bin_class->chunks) if ((chunk.sample >= start_sample) && (chunk.sample < end_sample)) { size += chunk.data.size(); matches++; } dest->resize(size); uint64_t offset = 0; uint64_t matches2 = 0; for (const DecodeBinaryDataChunk& chunk : bin_class->chunks) if ((chunk.sample >= start_sample) && (chunk.sample < end_sample)) { memcpy(dest->data() + offset, chunk.data.data(), chunk.data.size()); offset += chunk.data.size(); matches2++; // Make sure we don't overwrite memory if the array grew in the meanwhile if (matches2 == matches) break; } } catch (out_of_range&) { // Do nothing } } void DecodeSignal::get_merged_binary_data_chunks_by_offset(uint32_t segment_id, const Decoder* dec, uint32_t bin_class_id, uint64_t start, uint64_t end, vector *dest) const { assert(dest != nullptr); try { const DecodeSegment *segment = &(segments_.at(segment_id)); const DecodeBinaryClass* bin_class = nullptr; for (const DecodeBinaryClass& bc : segment->binary_classes) if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id)) bin_class = &bc; // Determine overall size before copying to resize dest vector only once uint64_t size = 0; uint64_t offset = 0; for (const DecodeBinaryDataChunk& chunk : bin_class->chunks) { if (offset >= start) size += chunk.data.size(); offset += chunk.data.size(); if (offset >= end) break; } dest->resize(size); offset = 0; uint64_t dest_offset = 0; for (const DecodeBinaryDataChunk& chunk : bin_class->chunks) { if (offset >= start) { memcpy(dest->data() + dest_offset, chunk.data.data(), chunk.data.size()); dest_offset += chunk.data.size(); } offset += chunk.data.size(); if (offset >= end) break; } } catch (out_of_range&) { // Do nothing } } const DecodeBinaryClass* DecodeSignal::get_binary_data_class(uint32_t segment_id, const Decoder* dec, uint32_t bin_class_id) const { try { const DecodeSegment *segment = &(segments_.at(segment_id)); for (const DecodeBinaryClass& bc : segment->binary_classes) if ((bc.decoder == dec) && (bc.info->bin_class_id == bin_class_id)) return &bc; } catch (out_of_range&) { // Do nothing } return nullptr; } 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->get_srd_decoder()->id); settings.setValue("visible", decoder->visible()); // Save decoder options const map& options = decoder->options(); settings.setValue("options", (int)options.size()); // Note: 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++; } // Save row properties i = 0; for (const Row* row : decoder->get_rows()) { settings.beginGroup("row" + QString::number(i)); settings.setValue("visible", row->visible()); settings.endGroup(); i++; } // Save class properties i = 0; for (const AnnotationClass* ann_class : decoder->ann_classes()) { settings.beginGroup("ann_class" + QString::number(i)); settings.setValue("visible", ann_class->visible); 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(), [&](decode::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); decoder->set_visible(settings.value("visible", true).toBool()); // 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(); // Restore row properties int i = 0; for (Row* row : decoder->get_rows()) { settings.beginGroup("row" + QString::number(i)); row->set_visible(settings.value("visible", true).toBool()); settings.endGroup(); i++; } // Restore class properties i = 0; for (AnnotationClass* ann_class : decoder->ann_classes()) { settings.beginGroup("ann_class" + QString::number(i)); ann_class->visible = settings.value("visible", true).toBool(); settings.endGroup(); i++; } break; } } settings.endGroup(); channels_updated(); } // Restore channel mapping unsigned int channels = settings.value("channels").toInt(); const vector< shared_ptr > signalbases = session_.signalbases(); for (unsigned int channel_id = 0; channel_id < channels; channel_id++) { auto channel = find_if(channels_.begin(), channels_.end(), [&](decode::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 decode::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 decode::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; } Decoder* DecodeSignal::get_decoder_by_instance(const srd_decoder *const srd_dec) { for (shared_ptr& d : stack_) if (d->get_srd_decoder() == srd_dec) return d.get(); return nullptr; } 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_dec = decoder->get_srd_decoder(); const GSList *l; // Mandatory channels for (l = srd_dec->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 (decode::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 decode::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_dec->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 (decode::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 decode::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 decode::DecodeChannel& p_ch = prev_channels[i]; const decode::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 (decode::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 (decode::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 (decode::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_pd_output_callback_add(srd_session_, SRD_OUTPUT_BINARY, DecodeSignal::binary_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 (decode::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_) for (Row* row : dec->get_rows()) segments_.back().annotation_rows.emplace(row, RowData(row)); // Prepare our binary output classes for (const shared_ptr& dec : stack_) { uint32_t n = dec->get_binary_class_count(); for (uint32_t i = 0; i < n; i++) segments_.back().binary_classes.push_back( {dec.get(), dec->get_binary_class(i), deque()}); } } 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_); // Get the decoder and the annotation data assert(pdata->pdo); assert(pdata->pdo->di); const srd_decoder *const srd_dec = pdata->pdo->di->decoder; assert(srd_dec); const srd_proto_data_annotation *const pda = (const srd_proto_data_annotation*)pdata->data; assert(pda); // Find the row Decoder* dec = ds->get_decoder_by_instance(srd_dec); assert(dec); AnnotationClass* ann_class = dec->get_ann_class_by_id(pda->ann_class); if (!ann_class) { qWarning() << "Decoder" << ds->display_name() << "wanted to add annotation" << "with class ID" << pda->ann_class << "but there are only" << dec->ann_classes().size() << "known classes"; return; } const Row* row = ann_class->row; if (!row) row = dec->get_row_by_id(0); // Add the annotation ds->segments_[ds->current_segment_id_].annotation_rows.at(row).emplace_annotation(pdata); } void DecodeSignal::binary_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; // Get the decoder and the binary data assert(pdata->pdo); assert(pdata->pdo->di); const srd_decoder *const srd_dec = pdata->pdo->di->decoder; assert(srd_dec); const srd_proto_data_binary *const pdb = (const srd_proto_data_binary*)pdata->data; assert(pdb); // Find the matching DecodeBinaryClass DecodeSegment* segment = &(ds->segments_.at(ds->current_segment_id_)); DecodeBinaryClass* bin_class = nullptr; for (DecodeBinaryClass& bc : segment->binary_classes) if ((bc.decoder->get_srd_decoder() == srd_dec) && (bc.info->bin_class_id == (uint32_t)pdb->bin_class)) bin_class = &bc; if (!bin_class) { qWarning() << "Could not find valid DecodeBinaryClass in segment" << ds->current_segment_id_ << "for binary class ID" << pdb->bin_class << ", segment only knows" << segment->binary_classes.size() << "classes"; return; } // Add the data chunk bin_class->chunks.emplace_back(); DecodeBinaryDataChunk* chunk = &(bin_class->chunks.back()); chunk->sample = pdata->start_sample; chunk->data.resize(pdb->size); memcpy(chunk->data.data(), pdb->data, pdb->size); Decoder* dec = ds->get_decoder_by_instance(srd_dec); ds->new_binary_data(ds->current_segment_id_, (void*)dec, pdb->bin_class); } 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.2/pv/data/decode/000700 001750 001750 00000000000 13640725776 015772 5ustar00uweuwe000000 000000 pulseview-0.4.2/pv/data/decode/row.hpp000600 001750 001750 00000003472 13640725356 017314 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 #include struct srd_decoder; struct srd_decoder_annotation_row; namespace pv { namespace data { namespace decode { struct AnnotationClass; class Decoder; class Row { public: Row(); Row(uint32_t index, Decoder* decoder, const srd_decoder_annotation_row* srd_row = nullptr); const Decoder* decoder() const; const srd_decoder_annotation_row* get_srd_row() const; const QString title() const; const QString description() const; vector ann_classes() const; uint32_t index() const; bool visible() const; void set_visible(bool visible); bool has_hidden_classes() const; bool operator<(const Row& other) const; bool operator==(const Row& other) const; private: uint32_t index_; Decoder* decoder_; const srd_decoder_annotation_row* srd_row_; bool visible_; }; } // namespace decode } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_DECODE_ROW_HPP pulseview-0.4.2/pv/data/decode/annotation.hpp000600 001750 001750 00000003315 13640725356 020653 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); Annotation(Annotation&& a); Annotation& operator=(Annotation&& a); ~Annotation(); uint64_t start_sample() const; uint64_t end_sample() const; Class ann_class_id() const; const QString ann_class_name() const; const vector* annotations() const; const Row* row() const; bool operator<(const Annotation &other) const; private: uint64_t start_sample_; uint64_t end_sample_; vector* annotations_; const Row *row_; Class ann_class_id_; }; } // namespace decode } // namespace data } // namespace pv #endif // PULSEVIEW_PV_VIEW_DECODE_ANNOTATION_HPP pulseview-0.4.2/pv/data/decode/decoder.hpp000600 001750 001750 00000006144 13640725356 020111 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 #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 { class Logic; class SignalBase; namespace decode { class Decoder; struct AnnotationClass { size_t id; char* name; char* description; Row* row; bool visible; }; 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 DecodeBinaryClassInfo { uint32_t bin_class_id; char* name; char* description; }; class Decoder { public: Decoder(const srd_decoder *const dec); virtual ~Decoder(); const srd_decoder* get_srd_decoder() const; const char* name() const; bool visible() const; void set_visible(bool visible); 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(); vector get_rows(); Row* get_row_by_id(size_t id); vector ann_classes() const; vector ann_classes(); AnnotationClass* get_ann_class_by_id(size_t id); const AnnotationClass* get_ann_class_by_id(size_t id) const; uint32_t get_binary_class_count() const; const DecodeBinaryClassInfo* get_binary_class(uint32_t id) const; private: const srd_decoder* const srd_decoder_; bool visible_; vector channels_; vector rows_; vector ann_classes_; vector bin_classes_; map options_; srd_decoder_inst *decoder_inst_; }; } // namespace decode } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_DECODE_DECODER_HPP pulseview-0.4.2/pv/data/decode/decoder.cpp000600 001750 001750 00000015157 13640725356 020110 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 std::map; using std::string; namespace pv { namespace data { namespace decode { Decoder::Decoder(const srd_decoder *const dec) : srd_decoder_(dec), visible_(true), decoder_inst_(nullptr) { // Query the annotation output classes uint32_t i = 0; for (GSList *l = dec->annotations; l; l = l->next) { char **ann_class = (char**)l->data; char *name = ann_class[0]; char *desc = ann_class[1]; ann_classes_.push_back({i++, name, desc, nullptr, true}); // Visible by default } // Query the binary output classes i = 0; for (GSList *l = dec->binary; l; l = l->next) { char **bin_class = (char**)l->data; char *name = bin_class[0]; char *desc = bin_class[1]; bin_classes_.push_back({i++, name, desc}); } // Query the annotation rows and reference them by the classes that use them uint32_t row_count = 0; for (const GSList *rl = srd_decoder_->annotation_rows; rl; rl = rl->next) row_count++; rows_.reserve(row_count); i = 0; for (const GSList *rl = srd_decoder_->annotation_rows; rl; rl = rl->next) { const srd_decoder_annotation_row *const srd_row = (srd_decoder_annotation_row *)rl->data; assert(srd_row); rows_.emplace_back(i++, this, srd_row); // FIXME PV can crash from .at() if a PD's ann classes are defined incorrectly for (const GSList *cl = srd_row->ann_classes; cl; cl = cl->next) ann_classes_.at((size_t)cl->data).row = &(rows_.back()); } if (rows_.empty()) { // Make sure there is a row for PDs without row declarations rows_.emplace_back(0, this); for (AnnotationClass& c : ann_classes_) c.row = &(rows_.back()); } } Decoder::~Decoder() { for (auto& option : options_) g_variant_unref(option.second); } const srd_decoder* Decoder::get_srd_decoder() const { return srd_decoder_; } const char* Decoder::name() const { return srd_decoder_->name; } bool Decoder::visible() const { return visible_; } void Decoder::set_visible(bool visible) { visible_ = visible; } 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, srd_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; } vector Decoder::get_rows() { vector result; for (Row& row : rows_) result.push_back(&row); return result; } Row* Decoder::get_row_by_id(size_t id) { if (id > rows_.size()) return nullptr; return &(rows_[id]); } vector Decoder::ann_classes() const { vector result; for (const AnnotationClass& c : ann_classes_) result.push_back(&c); return result; } vector Decoder::ann_classes() { vector result; for (AnnotationClass& c : ann_classes_) result.push_back(&c); return result; } AnnotationClass* Decoder::get_ann_class_by_id(size_t id) { if (id >= ann_classes_.size()) return nullptr; return &(ann_classes_[id]); } const AnnotationClass* Decoder::get_ann_class_by_id(size_t id) const { if (id >= ann_classes_.size()) return nullptr; return &(ann_classes_[id]); } uint32_t Decoder::get_binary_class_count() const { return bin_classes_.size(); } const DecodeBinaryClassInfo* Decoder::get_binary_class(uint32_t id) const { return &(bin_classes_.at(id)); } } // namespace decode } // namespace data } // namespace pv pulseview-0.4.2/pv/data/decode/annotation.cpp000600 001750 001750 00000005514 13640725356 020651 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 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_id_ = (Class)(pda->ann_class); annotations_ = new vector(); const char *const *annotations = (char**)pda->ann_text; while (*annotations) { annotations_->push_back(QString::fromUtf8(*annotations)); annotations++; } annotations_->shrink_to_fit(); } Annotation::Annotation(Annotation&& a) : start_sample_(a.start_sample_), end_sample_(a.end_sample_), annotations_(a.annotations_), row_(a.row_), ann_class_id_(a.ann_class_id_) { a.annotations_ = nullptr; } Annotation& Annotation::operator=(Annotation&& a) { if (&a != this) { if (annotations_) delete annotations_; start_sample_ = a.start_sample_; end_sample_ = a.end_sample_; annotations_ = a.annotations_; row_ = a.row_; ann_class_id_ = a.ann_class_id_; a.annotations_ = nullptr; } return *this; } Annotation::~Annotation() { if (annotations_) delete annotations_; } uint64_t Annotation::start_sample() const { return start_sample_; } uint64_t Annotation::end_sample() const { return end_sample_; } Annotation::Class Annotation::ann_class_id() const { return ann_class_id_; } const QString Annotation::ann_class_name() const { const AnnotationClass* ann_class = row_->decoder()->get_ann_class_by_id(ann_class_id_); return QString(ann_class->name); } 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.2/pv/data/decode/rowdata.cpp000600 001750 001750 00000006365 13640725356 020145 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 using std::vector; namespace pv { namespace data { namespace decode { RowData::RowData(Row* row) : row_(row), prev_ann_start_sample_(0) { assert(row); } uint64_t RowData::get_max_sample() const { if (annotations_.empty()) return 0; return annotations_.back().end_sample(); } uint64_t RowData::get_annotation_count() const { return annotations_.size(); } void RowData::get_annotation_subset( deque &dest, uint64_t start_sample, uint64_t end_sample) const { // Determine whether we must apply per-class filtering or not bool all_ann_classes_enabled = true; bool all_ann_classes_disabled = true; uint32_t max_ann_class_id = 0; for (AnnotationClass* c : row_->ann_classes()) { if (!c->visible) all_ann_classes_enabled = false; else all_ann_classes_disabled = false; if (c->id > max_ann_class_id) max_ann_class_id = c->id; } if (all_ann_classes_enabled) { // No filtering, send everyting out as-is for (const auto& annotation : annotations_) if ((annotation.end_sample() > start_sample) && (annotation.start_sample() <= end_sample)) dest.push_back(&annotation); } else { if (!all_ann_classes_disabled) { // Filter out invisible annotation classes vector class_visible; class_visible.resize(max_ann_class_id + 1, 0); for (AnnotationClass* c : row_->ann_classes()) if (c->visible) class_visible[c->id] = 1; for (const auto& annotation : annotations_) if ((class_visible[annotation.ann_class_id()]) && (annotation.end_sample() > start_sample) && (annotation.start_sample() <= end_sample)) dest.push_back(&annotation); } } } void RowData::emplace_annotation(srd_proto_data *pdata) { // We insert the annotation in a way so that the annotation list // is sorted by start sample. Otherwise, we'd have to sort when // painting, which is expensive if (pdata->start_sample < prev_ann_start_sample_) { // Find location to insert the annotation at auto it = annotations_.end(); do { it--; } while ((it->start_sample() > pdata->start_sample) && (it != annotations_.begin())); // Allow inserting at the front if (it != annotations_.begin()) it++; annotations_.emplace(it, pdata, row_); } else { annotations_.emplace_back(pdata, row_); prev_ann_start_sample_ = pdata->start_sample; } } } // namespace decode } // namespace data } // namespace pv pulseview-0.4.2/pv/data/decode/rowdata.hpp000600 001750 001750 00000003357 13640725356 020150 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 using std::deque; using std::vector; namespace pv { namespace data { namespace decode { class Row; class RowData { public: RowData(Row* row); uint64_t get_max_sample() const; uint64_t get_annotation_count() 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(deque &dest, uint64_t start_sample, uint64_t end_sample) const; void emplace_annotation(srd_proto_data *pdata); private: deque annotations_; Row* row_; uint64_t prev_ann_start_sample_; }; } // namespace decode } // namespace data } // namespace pv #endif // PULSEVIEW_PV_DATA_DECODE_ROWDATA_HPP pulseview-0.4.2/pv/data/decode/row.cpp000600 001750 001750 00000005621 13640725356 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 . */ #include #include "decoder.hpp" #include "row.hpp" #include namespace pv { namespace data { namespace decode { Row::Row() : index_(0), decoder_(nullptr), srd_row_(nullptr), visible_(true) { } Row::Row(uint32_t index, Decoder* decoder, const srd_decoder_annotation_row* srd_row) : index_(index), decoder_(decoder), srd_row_(srd_row), visible_(true) { } const Decoder* Row::decoder() const { return decoder_; } const srd_decoder_annotation_row* Row::get_srd_row() const { return srd_row_; } const QString Row::title() const { if (decoder_ && decoder_->name() && srd_row_ && srd_row_->desc) return QString("%1: %2") .arg(QString::fromUtf8(decoder_->name()), QString::fromUtf8(srd_row_->desc)); if (decoder_ && decoder_->name()) return QString::fromUtf8(decoder_->name()); if (srd_row_ && srd_row_->desc) return QString::fromUtf8(srd_row_->desc); return QString(); } const QString Row::description() const { if (srd_row_ && srd_row_->desc) return QString::fromUtf8(srd_row_->desc); return QString(); } vector Row::ann_classes() const { assert(decoder_); vector result; if (!srd_row_) { if (index_ == 0) { // When operating as the fallback row, all annotation classes belong to it return decoder_->ann_classes(); } return result; } for (GSList *l = srd_row_->ann_classes; l; l = l->next) { size_t class_id = (size_t)l->data; result.push_back(decoder_->get_ann_class_by_id(class_id)); } return result; } uint32_t Row::index() const { return index_; } bool Row::visible() const { return visible_; } void Row::set_visible(bool visible) { visible_ = visible; } bool Row::has_hidden_classes() const { for (const AnnotationClass* c : ann_classes()) if (!c->visible) return true; return false; } bool Row::operator<(const Row& other) const { return (decoder_ < other.decoder_) || (decoder_ == other.decoder_ && srd_row_ < other.srd_row_); } bool Row::operator==(const Row& other) const { return ((decoder_ == other.decoder()) && (srd_row_ == other.srd_row_)); } } // namespace decode } // namespace data } // namespace pv pulseview-0.4.2/pv/data/segment.hpp000600 001750 001750 00000006516 13640725356 016726 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.2/pv/data/signalbase.hpp000600 001750 001750 00000021713 13640725356 017370 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.2/pv/data/decodesignal.hpp000600 001750 001750 00000017122 13640725356 017700 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 #include #include using std::atomic; using std::condition_variable; using std::deque; using std::map; using std::mutex; using std::vector; using std::shared_ptr; using pv::data::decode::Annotation; using pv::data::decode::DecodeBinaryClassInfo; using pv::data::decode::DecodeChannel; using pv::data::decode::Decoder; using pv::data::decode::Row; using pv::data::decode::RowData; namespace pv { class Session; namespace data { class Logic; class LogicSegment; class SignalBase; class SignalData; struct DecodeBinaryDataChunk { vector data; uint64_t sample; ///< Number of the sample where this data was provided by the PD }; struct DecodeBinaryClass { const Decoder* decoder; const DecodeBinaryClassInfo* info; deque chunks; }; struct DecodeSegment { map annotation_rows; pv::util::Timestamp start_time; double samplerate; int64_t samples_decoded_incl, samples_decoded_excl; vector binary_classes; }; 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, bool restart_decode=true); 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 get_rows(bool visible_only=false); vector get_rows(bool visible_only=false) const; uint64_t get_annotation_count(const Row* row, uint32_t segment_id) 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(deque &dest, const 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(deque &dest, uint32_t segment_id, uint64_t start_sample, uint64_t end_sample) const; uint32_t get_binary_data_chunk_count(uint32_t segment_id, const Decoder* dec, uint32_t bin_class_id) const; void get_binary_data_chunk(uint32_t segment_id, const Decoder* dec, uint32_t bin_class_id, uint32_t chunk_id, const vector **dest, uint64_t *size); void get_merged_binary_data_chunks_by_sample(uint32_t segment_id, const Decoder* dec, uint32_t bin_class_id, uint64_t start_sample, uint64_t end_sample, vector *dest) const; void get_merged_binary_data_chunks_by_offset(uint32_t segment_id, const Decoder* dec, uint32_t bin_class_id, uint64_t start, uint64_t end, vector *dest) const; const DecodeBinaryClass* get_binary_data_class(uint32_t segment_id, const Decoder* dec, uint32_t bin_class_id) 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; Decoder* get_decoder_by_instance(const srd_decoder *const srd_dec); 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); static void binary_callback(srd_proto_data *pdata, void *decode_signal); Q_SIGNALS: void decoder_stacked(void* decoder); ///< decoder is of type decode::Decoder* void decoder_removed(void* decoder); ///< decoder is of type decode::Decoder* void new_annotations(); // TODO Supply segment for which they belong to void new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id); 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_; 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.2/pv/data/logicsegment.cpp000600 001750 001750 00000044501 13640725356 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 . */ #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.2/pv/data/analog.cpp000600 001750 001750 00000004021 13640725356 016505 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.2/pv/data/analogsegment.hpp000600 001750 001750 00000004611 13640725356 020102 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.2/pv/data/logic.hpp000600 001750 001750 00000003452 13640725356 016355 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.2/pv/data/signaldata.hpp000600 001750 001750 00000002670 13640725356 017370 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.2/pv/data/segment.cpp000600 001750 001750 00000016712 13640725356 016720 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.2/pv/data/signaldata.cpp000600 001750 001750 00000001545 13640725356 017363 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.2/pv/data/analogsegment.cpp000600 001750 001750 00000016460 13640725356 020102 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.2/pv/data/logicsegment.hpp000600 001750 001750 00000007155 13640725356 017744 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.2/pv/data/logic.cpp000600 001750 001750 00000004073 13640725356 016350 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.2/pv/data/analog.hpp000600 001750 001750 00000003510 13640725356 016514 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.2/pv/globalsettings.hpp000600 001750 001750 00000010432 13640725356 017364 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 #include "util.hpp" 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_Language; static const QString Key_General_Theme; static const QString Key_General_Style; static const QString Key_General_SaveWithSetup; 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_View_CursorShowInterval; static const QString Key_View_CursorShowFrequency; static const QString Key_View_CursorShowSamples; static const QString Key_Dec_InitialStateConfigurable; static const QString Key_Dec_ExportFormat; static const QString Key_Dec_AlwaysShowAllRows; 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(); static bool current_theme_is_dark(); void apply_theme(); void apply_language(); 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); static void store_timestamp(QSettings &settings, const char *name, const pv::util::Timestamp &ts); static pv::util::Timestamp restore_timestamp(QSettings &settings, const char *name); private: static vector callbacks_; static bool tracking_; static map tracked_changes_; static QString default_style_; static QPalette default_palette_; static bool is_dark_theme_; }; } // namespace pv #endif // PULSEVIEW_GLOBALSETTINGS_HPP pulseview-0.4.2/pv/globalsettings.cpp000600 001750 001750 00000032426 13640725356 017366 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 #include #include #include #include #include #include #include #include #include "globalsettings.hpp" #include "application.hpp" using std::map; using std::pair; using std::string; using std::stringstream; 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_Language = "General_Language"; const QString GlobalSettings::Key_General_Theme = "General_Theme"; const QString GlobalSettings::Key_General_Style = "General_Style"; const QString GlobalSettings::Key_General_SaveWithSetup = "General_SaveWithSetup"; 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_View_CursorShowFrequency = "View_CursorShowFrequency"; const QString GlobalSettings::Key_View_CursorShowInterval = "View_CursorShowInterval"; const QString GlobalSettings::Key_View_CursorShowSamples = "View_CursorShowSamples"; const QString GlobalSettings::Key_Dec_InitialStateConfigurable = "Dec_InitialStateConfigurable"; const QString GlobalSettings::Key_Dec_ExportFormat = "Dec_ExportFormat"; const QString GlobalSettings::Key_Dec_AlwaysShowAllRows = "Dec_AlwaysShowAllRows"; const QString GlobalSettings::Key_Log_BufferSize = "Log_BufferSize"; const QString GlobalSettings::Key_Log_NotifyOfStacktrace = "Log_NotifyOfStacktrace"; vector GlobalSettings::callbacks_; bool GlobalSettings::tracking_ = false; bool GlobalSettings::is_dark_theme_ = false; map GlobalSettings::tracked_changes_; QString GlobalSettings::default_style_; QPalette GlobalSettings::default_palette_; GlobalSettings::GlobalSettings() : QSettings() { 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() { if (!contains(Key_General_Language)) { // Determine and set default UI language QString language = QLocale().uiLanguages().first(); // May return e.g. en-Latn-US // clazy:exclude=detaching-temporary language = language.split("-").first(); setValue(Key_General_Language, language); apply_language(); } // Use no theme by default if (!contains(Key_General_Theme)) setValue(Key_General_Theme, 0); if (!contains(Key_General_Style)) setValue(Key_General_Style, ""); // Save setup with .sr files by default if (!contains(Key_General_SaveWithSetup)) setValue(Key_General_SaveWithSetup, true); // 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_View_CursorShowInterval)) setValue(Key_View_CursorShowInterval, true); if (!contains(Key_View_CursorShowFrequency)) setValue(Key_View_CursorShowFrequency, true); // %c was used for the row name in the past so we need to transition such users if (!contains(Key_Dec_ExportFormat) || value(Key_Dec_ExportFormat).toString() == "%s %d: %c: %1") setValue(Key_Dec_ExportFormat, "%s %d: %r: %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::apply_language() { Application* a = qobject_cast(QApplication::instance()); a->switch_language(value(Key_General_Language).toString()); } 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; } void GlobalSettings::store_timestamp(QSettings &settings, const char *name, const pv::util::Timestamp &ts) { stringstream ss; boost::archive::text_oarchive oa(ss); oa << boost::serialization::make_nvp(name, ts); settings.setValue(name, QString::fromStdString(ss.str())); } pv::util::Timestamp GlobalSettings::restore_timestamp(QSettings &settings, const char *name) { util::Timestamp result; stringstream ss; ss << settings.value(name).toString().toStdString(); try { boost::archive::text_iarchive ia(ss); ia >> boost::serialization::make_nvp(name, result); } catch (boost::archive::archive_exception&) { qDebug() << "Could not restore setting" << name; } return result; } } // namespace pv pulseview-0.4.2/pv/storesession.hpp000600 001750 001750 00000005053 13640725356 017106 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.2/pv/devicemanager.hpp000600 001750 001750 00000004761 13640725356 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 . */ #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.2/pv/prop/000700 001750 001750 00000000000 13640725776 014616 5ustar00uweuwe000000 000000 pulseview-0.4.2/pv/prop/enum.hpp000600 001750 001750 00000003306 13640725356 016271 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.2/pv/prop/string.cpp000600 001750 001750 00000004553 13640725356 016633 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.2/pv/prop/int.cpp000600 001750 001750 00000013000 13640725356 016102 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.2/pv/prop/bool.hpp000600 001750 001750 00000002512 13640725356 016256 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.2/pv/prop/double.hpp000600 001750 001750 00000003207 13640725356 016577 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.2/pv/prop/enum.cpp000600 001750 001750 00000016362 13640725356 016272 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.2/pv/prop/property.cpp000600 001750 001750 00000002246 13640725356 017206 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.2/pv/prop/int.hpp000600 001750 001750 00000003032 13640725356 016113 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.2/pv/prop/bool.cpp000600 001750 001750 00000004515 13640725356 016256 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.2/pv/prop/property.hpp000600 001750 001750 00000003447 13640725356 017217 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.2/pv/prop/string.hpp000600 001750 001750 00000002442 13640725356 016633 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.2/pv/prop/double.cpp000600 001750 001750 00000005137 13640725356 016576 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.2/pv/session.cpp000600 001750 001750 00000115610 13640725356 016025 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 "util.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_FLOW #include #include #endif #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::list; using std::lock_guard; 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; #ifdef ENABLE_FLOW using std::unique_lock; #endif using std::unique_ptr; 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; #ifdef ENABLE_FLOW using Gst::Bus; using Gst::ElementFactory; using Gst::Pipeline; #endif using pv::util::Timestamp; using pv::views::trace::Signal; using pv::views::trace::AnalogSignal; using pv::views::trace::LogicSignal; 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 vector< 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_setup(QSettings &settings) const { int i = 0; // Save channels and decoders for (const shared_ptr& base : signalbases_) { #ifdef ENABLE_DECODE if (base->is_decode_signal()) { settings.beginGroup("decode_signal" + QString::number(i++)); base->save_settings(settings); settings.endGroup(); } else #endif { settings.beginGroup(base->internal_name()); base->save_settings(settings); settings.endGroup(); } } settings.setValue("decode_signals", i); // Save view states and their signal settings // Note: main_view must be saved as view0 i = 0; settings.beginGroup("view" + QString::number(i++)); main_view_->save_settings(settings); settings.endGroup(); for (const shared_ptr& view : views_) { if (view != main_view_) { settings.beginGroup("view" + QString::number(i++)); settings.setValue("type", view->get_type()); view->save_settings(settings); settings.endGroup(); } } settings.setValue("views", i); int view_id = 0; i = 0; for (const shared_ptr& vb : views_) { shared_ptr tv = dynamic_pointer_cast(vb); if (tv) { for (const shared_ptr& time_item : tv->time_items()) { const shared_ptr flag = dynamic_pointer_cast(time_item); if (flag) { if (!flag->enabled()) continue; settings.beginGroup("meta_obj" + QString::number(i++)); settings.setValue("type", "time_marker"); settings.setValue("assoc_view", view_id); GlobalSettings::store_timestamp(settings, "time", flag->time()); settings.setValue("text", flag->get_text()); settings.endGroup(); } } if (tv->cursors_shown()) { settings.beginGroup("meta_obj" + QString::number(i++)); settings.setValue("type", "selection"); settings.setValue("assoc_view", view_id); const shared_ptr cp = tv->cursors(); GlobalSettings::store_timestamp(settings, "start_time", cp->first()->time()); GlobalSettings::store_timestamp(settings, "end_time", cp->second()->time()); settings.endGroup(); } } view_id++; } settings.setValue("meta_objs", i); } void Session::save_settings(QSettings &settings) const { map dev_info; list key_list; 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_setup(settings); } } void Session::restore_setup(QSettings &settings) { // 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(type, this); views_.back()->restore_settings(settings); } else main_view_->restore_settings(settings); settings.endGroup(); } // Restore meta objects like markers and cursors int meta_objs = settings.value("meta_objs").toInt(); for (int i = 0; i < meta_objs; i++) { settings.beginGroup("meta_obj" + QString::number(i)); shared_ptr vb; shared_ptr tv; if (settings.contains("assoc_view")) vb = views_.at(settings.value("assoc_view").toInt()); if (vb) tv = dynamic_pointer_cast(vb); const QString type = settings.value("type").toString(); if ((type == "time_marker") && tv) { Timestamp ts = GlobalSettings::restore_timestamp(settings, "time"); shared_ptr flag = tv->add_flag(ts); flag->set_text(settings.value("text").toString()); } if ((type == "selection") && tv) { Timestamp start = GlobalSettings::restore_timestamp(settings, "start_time"); Timestamp end = GlobalSettings::restore_timestamp(settings, "end_time"); tv->set_cursors(start, end); tv->show_cursors(); } settings.endGroup(); } } void Session::restore_settings(QSettings &settings) { shared_ptr device; const 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"); const 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_setup(settings); } 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); } bool Session::using_file_device() const { shared_ptr sessionfile_device = dynamic_pointer_cast(device_); shared_ptr inputfile_device = dynamic_pointer_cast(device_); return (sessionfile_device || inputfile_device); } /** * 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