pulseview-0.4.2/l10n/ 000700 001750 001750 00000000000 13640725776 013763 5 ustar 00uwe uwe 000000 000000 pulseview-0.4.2/l10n/de.ts 000600 001750 001750 00000201742 13640725356 014725 0 ustar 00uwe uwe 000000 000000
ApplicationSome 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.QApplicationSelect a decoder to see its description here.Wähle einen Dekoder, um dessen Beschreibung hier lesen zu können.Session %1Analysesitzung %1Querying config key %1 is not allowedInternal messageQuerying config key %1 resulted in %2Internal messageUnknown type supplied when attempting to query %1Internal messageError when scanning device driver '%1': %2Internal messageQHexViewNo data availableKeine Daten vorhandenQObjectCancelAbbrechenScanning 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 messageDon't show this message againDiese Meldung in Zukunft nicht mehr anzeigenWhen %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 messageSubWindow<p align='right'>Tags: %1</p><p align='right'>Stichworte: %1</p>pv::MainWindowPulseViewNameDecoder SelectorProtokolldekoderSession %1Analysesitzung %1Create New SessionNeue AnalysesitzungStart/Stop AcquisitionDatenerfassung starten/stoppenSettingsEinstellungenReloadNeu ladenConfirmationBestätigungThere is unsaved data. Close anyway?Es gibt noch ungespeicherte Daten. Trotzdem beenden?RunStartenStopStoppenThis session contains unsaved data. Close it anyway?Die Daten dieser Analysesitzung wurden nicht gespeichert. Trotzdem schließen?pv::SessionFailed to select deviceFehler beim Auswählen des GerätesFailed to open deviceFehler beim Öffnen des GerätesErrorFehlerUnexpected input format: %sUnerwartetes Importformat: %sFailed to load %1Fehler beim Laden von %1No 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 messagepv::StoreSessionCan'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::DecodeSignalNo decodersKeine ProtokolldekoderThere are no channels assigned to this decoderDem Protokolldekoder sind keine Kanäle zugeordnetOne or more required channels have not been specifiedMindestens ein notwendiger Kanal wurde noch nicht zugeordnetNo input dataKeine Daten zum Auswerten vorhandenDecoder reported an errorProtokolldekoder meldet FehlerFailed to create decoder instanceFehler beim Erzeugen des Protokolldekoderspv::data::SignalBaseSignal averageDurchschnittlicher Signalpegel0.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 aboveNach Geräten &suchen, die der ausgewählte Treiber ansprechen kannConnect to DeviceMit Gerät verbindenStep 1: Choose the driverSchritt 1: Treiber auswählen&USBSerial &PortSerielle Sch&nittstelle&TCP/IPProtocol:Protokoll:Step 2: Choose the interfaceSchritt 2: Schnittstelle auswählenStep 3: Scan for devicesSchritt 3: Nach Geräten suchenStep 4: Select the deviceSchritt 4: Gerät auswählenpv::dialogs::SettingsGeneralAllgemeinViewsAnsichtenDecodersProtokolldekoderAboutProgrammdetailsLoggingProgramminterne MeldungenUser interface languageSprache der BenutzeroberflächeUser interface themeDesign 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 DefaultStandardQt widget styleQt-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 fileAnalyse&sitzungs-Konfiguration zusammen mit .sr-Dateien speichernTrace ViewSignalansichtUse colored trace &backgroundVerwende &farbigen KanalhintergrundConstantly perform &zoom-to-fit during acquisitionStändig den &Zoom anpassen, während Daten aufgezeichnet werdenPerform a zoom-to-&fit when acquisition stopsDen Zoom &anpassen, wenn die Datenerfassung stopptShow time zero at the triggerDen Triggerzeitpunkt automatisch als Nullpunkt festlegenAlways keep &newest samples at the right edge during captureDie neuesten Datenpunkte während der Aufzeichnung immer am rechten &Rand anzeigenShow data &sampling pointsDaten&punkte visuell hervorhebenFill high areas of logic signalsHigh-Pegel von Logiksignalen hervorhebenColor to fill high areas of logic signals withFarbe für hervorgehobene High-PegelShow analog minor grid in addition to div gridVertikale Unterteilungen nochmals unterteilenHighlight mouse cursor using a vertical marker linePosition des Mauscursors durch vertikalen Balken hervorheben pixels PixelMaximum distance from edges before markers snap to themAbstand zu Signalflanken, bevor Markierer einrastenColor to fill cursor area withFarbe für die Auswahl-MarkierungNoneKeineBackgroundHintergrundfarbeDotsFarbige AbtastpunkteConversion threshold display mode (analog traces only)Darstellung von Konvertierungsschwellen (nur für analoge Kanäle)Default analog trace div heightStandardgröße von analogen KanälenDefault logic trace heightStandardgröße von LogikkanälenAllow configuration of &initial signal state&Initialzustände von Signalen konfigurierbar machenAlways show all &rows, even if no annotation is visibleImmer alle &Reihen anzeigen, auch wenn hierfür keine dekodierten Werte vorliegenAnnotation export formatFormat 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 laterGNU GPL, Version 3 oder neuerVersions, 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 ZeilenLength of background buffer:Länge des Logpuffers:&Save to File&Speichern&Pop out&AbdockenYou 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 LogLog speichernLog Files (*.txt *.log);;All Files (*)Logdateien (*.txt *.log);;Alle Dateien (*)SuccessErfolgLog saved to %1.Log als %1 gespeichert.ErrorFehlerFile %1 could not be written to.Konnte Datei %1 nicht speichern.%1 Logpv::dialogs::StoreProgressSaving...Speichere...CancelAbbrechenFailed to save session.Beim Speichern trat ein Fehler auf.pv::popups::ChannelsAllAlleLogicLogikAnalogAnalogNamedBenamteUnnamedUnbenamteChangingSich änderndeNon-changingKonstanteDisable: Deaktivieren: Enable: Aktivieren: NoneKeinepv::prop::BoolQuerying config key %1 resulted in %2Internal messagepv::prop::DoubleQuerying config key %1 resulted in %2Internal messagepv::prop::EnumQuerying config key %1 resulted in %2Internal messagepv::prop::IntQuerying config key %1 resulted in %2Internal messagepv::prop::StringQuerying config key %1 resulted in %2Internal messagepv::subwindows::decoder_selector::DecoderCollectionModelDecoderDekoderNameIDAll DecodersAlle Dekoderpv::subwindows::decoder_selector::SubWindowSelect 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 DecoderWähle Protokolldekoderpv::toolbars::MainBarNew &ViewNeue &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 decoderProtokolldekoder hinzufügenConfigure DeviceGerät konfigurierenConfigure ChannelsKanäle konfigurierenFailed 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 CursorsFehlende AuswahlYou 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 RangeAuswahl ungültigThe cursors don't define a valid range of samples.Die Auswahl-Markierer geben keinen gültigen Datenbereich an.%1 files %1-Dateien All FilesAlle DateienSave FileSpeichernExport %1%1 exportieren%1 files%1-DateienImport FileDateiimportImport %1%1 importierenOpen FileÖffnensigrok Sessions (*.sr);;All Files (*)sigrok-Datenformat (*.sr);;Alle Dateien (*)PulseView Session Setups (*.pvs);;All Files (*)Analysesitzungs-Konfigurationen (*.pvs);;Alle Dateien (*)Total sampling time: %1Internal messagepv::views::decoder_binary::ViewDecoder:Dekoder:Show data asZeige Daten alsHexdumpHex-Dump&Save...&Speichern...Save Binary DataBinäre Daten speichernBinary Data Files (*.bin);;All Files (*)Binärdateien (*.bin);;Alle Dateien (*)ErrorFehlerFile %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::ViewDecoder:Dekoder:Show data asZeige Daten alsHexdumpHex-Dump&Save...&Speichern...Save Binary DataBinäre Daten speichernBinary Data Files (*.bin);;All Files (*)Binärdateien (*.bin);;Alle Dateien (*)ErrorFehlerFile %1 could not be written to.Konnte Datei %1 nicht speichern.Hex Dumps (*.txt);;All Files (*)Hex-Dumps (*.txt);;Alle Dateien (*)pv::views::trace::AnalogSignalNumber of pos vertical divsAnzahl Unterteilungen im PositivenNumber of neg vertical divsAnzahl Unterteilungen im Negativen pixels PixelDiv heightHöhe einer UnterteilungV/divV/divVertical resolutionVertikale AuflösungAutorangingAutomatische Skalierungnonekeineto logic via thresholdzu Logik mittels Schwellwertto logic via schmitt-triggerzu Logik mittels Schmitt-TriggerConversionKonvertierungConversion threshold(s)Konvertierungs-Schwellwert(e)analognur analogconvertednur konvertiertanalog+convertedanalog+konvertiertShow traces forAnzuzeigende Signalepv::views::trace::CursorDisable snappingEinrasten deaktivierenpv::views::trace::CursorPairDisplay intervalIntervall anzeigenDisplay frequencyFrequenz anzeigenDisplay samplesSamples anzeigenpv::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 DecoderProtokolldekoder stapelnStack a higher-level decoder on top of this oneWeiteren Protokolldekoder auf diesen stapelnDeleteLöschenResume decodingDekodierung fortsetzenPause decodingDekodierung anhaltenCopy annotation text to clipboardDekodierten Wert in die Zwischenablage kopierenExport all annotationsAlle dekodierten Werte exportierenExport all annotations for this rowAlle dekodierten Werte dieser Zeile exportierenExport all annotations, starting hereAlle dekodierten Werte ab hier exportierenExport annotations for this row, starting hereAlle dekodierten Werte dieser Zeile ab hier exportierenExport all annotations within cursor rangeAlle dekodierten Werte innerhalb des gewählten Bereiches exportierenExport annotations for this row within cursor rangeAlle dekodierten Werte dieser Zeile innerhalb des gewählten Bereiches exportieren%1:
%2<b>%1</b> (%2) %3Export annotationsDekodierte Werte exportierenText Files (*.txt);;All Files (*)Textdateien (*.txt);;Alle Dateien (*)ErrorFehlerFile %1 could not be written to.Konnte Datei %1 nicht speichern.Show this rowDiese Zeile anzeigenShow AllAlle anzeigenHide AllAlle versteckenpv::views::trace::FlagTextDeleteLöschenDisable snappingEinrasten deaktivierenpv::views::trace::HeaderGroupGruppierenpv::views::trace::LogicSignalNo triggerKein TriggerTrigger on rising edgeTrigger auf steigende FlankeTrigger on high levelTrigger auf High-PegelTrigger on falling edgeTrigger auf fallende FlankeTrigger on low levelTrigger auf Low-PegelTrigger on rising or falling edgeTrigger auf steigende oder fallende Flanke pixels PixelTrace heightKanalgrößeTriggerTriggerpv::views::trace::RulerCreate marker hereHier neue Markierung anlegenSet as zero pointAls Nullpunkt setzenReset zero pointNullpunkt zurücksetzenDisable mouse hover markerMauszeigerbalken deaktivierenEnable mouse hover markerMauszeigerbalken aktivierenpv::views::trace::SignalNameDisableDeaktivierenpv::views::trace::StandardBarZoom &InH&ineinzoomenZoom &OutHera&uszoomenZoom to &Fit&Passend zoomenShow &Cursors&Auswahl-Markierer anzeigenDisplay last segment onlyNur letztes Segment anzeigenDisplay last complete segment onlyNur letztes vollständiges Segment anzeigenDisplay a single segmentEinzelnes Segment anzeigenpv::views::trace::TimeMarkerTimeZeitpv::views::trace::TraceCreate marker hereHier neue Markierung anlegenColorFarbeNamepv::views::trace::TraceGroupUngroupTrennenpv::widgets::DecoderGroupBoxShow/hide this decoder traceDekoder anzeigen/verbergenDelete this decoder traceProtokolldekoder entfernenpv::widgets::DeviceToolButton<No Device><Kein Gerät>pv::widgets::ExportMenuExport %1...%1 exportieren...pv::widgets::ImportMenuImport %1...%1 importieren...
pulseview-0.4.2/pv/ 000700 001750 001750 00000000000 13640725776 013636 5 ustar 00uwe uwe 000000 000000 pulseview-0.4.2/pv/strnatcmp.hpp 000600 001750 001750 00000007162 13640725356 016364 0 ustar 00uwe uwe 000000 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.hpp 000600 001750 001750 00000004414 13640725356 016651 0 ustar 00uwe uwe 000000 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 5 ustar 00uwe uwe 000000 000000 pulseview-0.4.2/pv/views/viewbase.cpp 000600 001750 001750 00000010736 13640725356 017307 0 ustar 00uwe uwe 000000 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 5 ustar 00uwe uwe 000000 000000 pulseview-0.4.2/pv/views/decoder_binary/QHexView.cpp 000600 001750 001750 00000044545 13640725356 022160 0 ustar 00uwe uwe 000000 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.hpp 000600 001750 001750 00000006223 13640725356 022154 0 ustar 00uwe uwe 000000 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.cpp 000600 001750 001750 00000032133 13640725356 021420 0 ustar 00uwe uwe 000000 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.hpp 000600 001750 001750 00000006142 13640725356 021426 0 ustar 00uwe uwe 000000 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.hpp 000600 001750 001750 00000007031 13640725356 017306 0 ustar 00uwe uwe 000000 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 5 ustar 00uwe uwe 000000 000000 pulseview-0.4.2/pv/views/trace/signal.hpp 000600 001750 001750 00000006124 13640725356 020056 0 ustar 00uwe uwe 000000 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.hpp 000600 001750 001750 00000013246 13640725356 017702 0 ustar 00uwe uwe 000000 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.cpp 000600 001750 001750 00000022403 13640725356 021057 0 ustar 00uwe uwe 000000 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.hpp 000600 001750 001750 00000002301 13640725356 021247 0 ustar 00uwe uwe 000000 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.cpp 000600 001750 001750 00000002645 13640725356 021504 0 ustar 00uwe uwe 000000 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.hpp 000600 001750 001750 00000004603 13640725356 020031 0 ustar 00uwe uwe 000000 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.cpp 000600 001750 001750 00000007306 13640725356 022507 0 ustar 00uwe uwe 000000 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.cpp 000600 001750 001750 00000154277 13640725356 021053 0 ustar 00uwe uwe 000000 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.hpp 000600 001750 001750 00000010677 13640725356 021104 0 ustar 00uwe uwe 000000 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