From 2a613ec2c8bba16d2dfc55055298f0df3edc6afe Mon Sep 17 00:00:00 2001 From: Clint Banzhaf Date: Tue, 11 Jul 2023 12:15:11 +0200 Subject: [PATCH 1/6] set version to 1.7.4 --- src/Engine/Version.h | 2 +- src/QtBase/Version.h | 2 +- src/Studio/Version.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Engine/Version.h b/src/Engine/Version.h index 8f9c6866..d530f3bc 100644 --- a/src/Engine/Version.h +++ b/src/Engine/Version.h @@ -26,6 +26,6 @@ #define NEUROMORE_ENGINE_VERSION_MAJOR 1 #define NEUROMORE_ENGINE_VERSION_MINOR 7 -#define NEUROMORE_ENGINE_VERSION_PATCH 3 +#define NEUROMORE_ENGINE_VERSION_PATCH 4 #endif diff --git a/src/QtBase/Version.h b/src/QtBase/Version.h index 97bcc425..a9b53d1b 100644 --- a/src/QtBase/Version.h +++ b/src/QtBase/Version.h @@ -9,6 +9,6 @@ #define NEUROMORE_QTBASE_VERSION_MAJOR 1 #define NEUROMORE_QTBASE_VERSION_MINOR 7 -#define NEUROMORE_QTBASE_VERSION_PATCH 3 +#define NEUROMORE_QTBASE_VERSION_PATCH 4 #endif \ No newline at end of file diff --git a/src/Studio/Version.h b/src/Studio/Version.h index 4545d248..3ed11bb4 100644 --- a/src/Studio/Version.h +++ b/src/Studio/Version.h @@ -26,7 +26,7 @@ #define NEUROMORE_STUDIO_VERSION_MAJOR 1 #define NEUROMORE_STUDIO_VERSION_MINOR 7 -#define NEUROMORE_STUDIO_VERSION_PATCH 3 +#define NEUROMORE_STUDIO_VERSION_PATCH 4 // Macros From 1e5a4df692be52dd2699ef4777e307c4e64bd011 Mon Sep 17 00:00:00 2001 From: Clint Banzhaf Date: Tue, 25 Jul 2023 12:34:31 +0200 Subject: [PATCH 2/6] Full Folder Download (#302) --- .../BackendFileSystemWidget.cpp | 192 +++++++++++++----- .../BackendFileSystemWidget.h | 2 + 2 files changed, 138 insertions(+), 56 deletions(-) diff --git a/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.cpp b/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.cpp index 32decd8b..87ae4f2e 100644 --- a/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.cpp +++ b/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.cpp @@ -811,6 +811,13 @@ void BackendFileSystemWidget::OnContextMenu(const QPoint& point) QAction* removeAction = menu.addAction("&Delete Folder", this, SLOT(OnRemoveItem()) ); removeAction->setIcon( GetQtBaseManager()->FindIcon("Images/Icons/Minus.png") ); } + + if (singleSelectedItem->GetCreud().Read() == true) + { + // save JSON to disk + QAction* saveToDiskAction = menu.addAction("Download", this, SLOT(OnSaveToDisk())); + saveToDiskAction->setIcon(GetQtBaseManager()->FindIcon("Images/Icons/SaveAs.png")); + } } else { @@ -846,7 +853,7 @@ void BackendFileSystemWidget::OnContextMenu(const QPoint& point) copyJsonToClipboardAction->setIcon( GetQtBaseManager()->FindIcon("Images/Icons/Copy.png") ); // save JSON to disk - QAction* saveToDiskAction = menu.addAction("Save To HDD", this, SLOT(OnSaveToDisk()) ); + QAction* saveToDiskAction = menu.addAction("Download", this, SLOT(OnSaveToDisk()) ); saveToDiskAction->setIcon( GetQtBaseManager()->FindIcon("Images/Icons/SaveAs.png") ); // load JSON from disk and save it to the cloud @@ -1136,69 +1143,142 @@ void BackendFileSystemWidget::OnCopyJsonToClipboard() // save JSON to a disk on the local hard drive void BackendFileSystemWidget::OnSaveToDisk() { - // get the selected item and return directly in case no single selected item is selected - SelectionItem* selectedItem = GetSingleSelectionItem(); - if (selectedItem == NULL || selectedItem->IsFolder() == true) - { - QMessageBox::critical( this, "ERROR", "Cannot save graph JSON to local hard disk. No or multiple files selected." ); - return; - } + const QList selectedItems = + mTreeWidget->selectedItems(); - - // get the filename where to save it - QString defaultFileName = mLastSelectedFileDialogFolder + "\\" + selectedItem->GetName(); - QString selectedFilter; - QFileDialog::Options options; - QString filename = QFileDialog::getSaveFileName( this, // parent - "Save", // caption - defaultFileName, // suggested file name and directory - "JSON (*.json)", - &selectedFilter, - options ); - if (filename.isEmpty() == true) - return; + // only single selection supported + if (selectedItems.count() != 1) + return; - mLastSelectedFileDialogFolder = QFileInfo(filename).absolutePath(); + QTreeWidgetItem* rootItem = selectedItems[0]; + SelectionItem rootModel = CreateSelectionItem(rootItem); - // save EXPERIENCE - Experience* experience = GetEngine()->GetActiveExperience(); - if (experience != NULL && experience->GetUuidString().IsEqual(selectedItem->GetUuidString()) == true) - { - // save the experience - Json jsonParser; - Json::Item rootItem = jsonParser.GetRootItem(); - experience->Save(jsonParser, rootItem); // Note: add parameters to export whole design incl. attached classifier/statemachine here - bool result = jsonParser.WriteToFile(FromQtString(filename).AsChar(), true); - if (result == false) - { - QMessageBox::critical( this, "ERROR", "Cannot save design JSON to local hard disk." ); - return; - } - } - // save GRAPH - else - { - // find the graph based on the uuid - Graph* graph = GetGraphManager()->FindGraphByUuid( selectedItem->GetUuid() ); - if (graph == NULL) - { - QMessageBox::critical( this, "ERROR", "Cannot save graph JSON to local hard disk. Different file selected than the shown graph in the graph window." ); - return; - } + // download single file + if (!rootModel.IsFolder()) + { + // show file selection dialog + const QString defaultName = mLastSelectedFileDialogFolder + '/' + rootModel.GetName() + rootModel.GetExtension().AsChar(); + const QString ext((rootModel.GetTypeString() + " (*" + rootModel.GetExtension() + ")").AsChar()); + const QString filename = QFileDialog::getSaveFileName(this, + "Save", defaultName, ext); + + if (filename.isEmpty()) + return; + + // remember selected path + mLastSelectedFileDialogFolder = QFileInfo(filename).absolutePath(); + + // download file + FilesGetRequest request(GetUser()->GetToken(), rootModel.GetUuid()); + QNetworkReply* reply = GetBackendInterface()->GetNetworkAccessManager()->ProcessRequest(request); + connect(reply, &QNetworkReply::finished, this, [reply, this, filename]() + { + Json json; + QNetworkReply* networkReply = qobject_cast(sender()); + FilesGetResponse response(networkReply); + if (response.HasError()) + { + QMessageBox::warning(this, "Error", "Download failed", QMessageBox::Ok); + return; + } + if (!json.Parse(response.GetJsonContent())) + { + QMessageBox::warning(this, "Error", "JSON Parse failed", QMessageBox::Ok); + return; + } + if (!json.WriteToFile(filename.toLatin1().data(), true)) + { + QMessageBox::warning(this, "Error", "File Write failed", QMessageBox::Ok); + return; + } + }); + } - // save the currently shown graph and save it as a string - bool result = GraphExporter::Save( FromQtString(filename).AsChar(), graph ); - if (result == false) - { - QMessageBox::critical( this, "ERROR", "Cannot save graph JSON to local hard disk. Saving the graph failed." ); - return; - } - } + // download folder + else + { + if (!rootItem->childCount()) + return; + // show folder selection dialog + const QString defaultName = mLastSelectedFileDialogFolder + '/' + rootModel.GetName(); + const QString folder = QFileDialog::getExistingDirectory( + this, "Select Folder", defaultName); + if (folder.isEmpty()) + return; + // remember selected path + mLastSelectedFileDialogFolder = QFileInfo(folder).absolutePath(); - + // shared root path of all elements + const Core::String& rootPath = rootModel.GetPathString(); + const uint32 rootPathLength = rootPath.GetLength(); + + SelectionItem model; + QDir dir; + std::vector stack; + Core::String path; + + // start with root folder + stack.push_back(rootItem); + + // tree traversal + while (!stack.empty()) + { + // pop next + auto* itm = stack.back(); + stack.pop_back(); + + // parse model from qt instance + model = CreateSelectionItem(itm); + + // folder + if (model.IsFolder()) + { + // add subitems + int num = itm->childCount(); + for (int i = 0; i < num; i++) + stack.push_back(itm->child(i)); + } + + // file + else + { + path = model.GetPathString(); + path.Remove(0, rootPathLength); + path = FromQtString(folder) + '/' + path; + + if (dir.mkpath(path.AsChar())) + { + const Core::String filename = path + model.GetNameString() + model.GetExtension(); + FilesGetRequest request(GetUser()->GetToken(), model.GetUuid()); + QNetworkReply* reply = GetBackendInterface()->GetNetworkAccessManager()->ProcessRequest(request); + connect(reply, &QNetworkReply::finished, this, [reply, this, filename]() + { + Json json; + QNetworkReply* networkReply = qobject_cast(sender()); + FilesGetResponse response(networkReply); + if (response.HasError()) + { + QMessageBox::warning(this, "Error", "Download failed", QMessageBox::Ok); + return; + } + if (!json.Parse(response.GetJsonContent())) + { + QMessageBox::warning(this, "Error", "JSON Parse failed", QMessageBox::Ok); + return; + } + if (!json.WriteToFile(filename.AsChar(), true)) + { + QMessageBox::warning(this, "Error", "File Write failed", QMessageBox::Ok); + return; + } + }); + } + } + } + } } diff --git a/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.h b/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.h index d4669ba9..41bbf629 100644 --- a/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.h +++ b/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.h @@ -85,6 +85,8 @@ class BackendFileSystemWidget : public QWidget inline bool operator==(const SelectionItem& item) const { return ( (mName==item.mName) && (mPath==item.mPath) && (mType==item.mType) && (mUuid == item.mUuid) && (mCreud == item.mCreud) && (mRevision == item.mRevision)); } inline bool IsValid() const { return ( (GetNameString().IsEmpty() == false && GetUuidString().IsEmpty() == false) ); } + inline Core::String GetExtension() const { return GetTypeString() == "CLASSIFIER" ? ".cs.json" : GetTypeString() == "STATEMACHINE" ? ".sm.json" : GetTypeString() == "EXPERIENCE" ? ".xp.json" : ".json"; } + const Creud& GetCreud() const { return mCreud; } bool IsFolder() const { return GetTypeString().IsEqual(FOLDER_TYPE) == true; } From 79bcc5f7e034ecf6331b8020cd9c09a631ae622a Mon Sep 17 00:00:00 2001 From: Clint Banzhaf Date: Mon, 31 Jul 2023 09:59:59 +0200 Subject: [PATCH 3/6] Tweaks for OpenBCI with Daisy (#303) --- src/Engine/Devices/OpenBCI/OpenBCIDevices.cpp | 32 +++++++++---------- src/Engine/Devices/OpenBCI/OpenBCIDevices.h | 3 +- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/Engine/Devices/OpenBCI/OpenBCIDevices.cpp b/src/Engine/Devices/OpenBCI/OpenBCIDevices.cpp index 23d20e63..accdeb14 100644 --- a/src/Engine/Devices/OpenBCI/OpenBCIDevices.cpp +++ b/src/Engine/Devices/OpenBCI/OpenBCIDevices.cpp @@ -139,22 +139,22 @@ void OpenBCIDaisyDevice::CreateElectrodes() mElectrodes.Clear(); mElectrodes.Reserve(NUMELECTRODESDAISY); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("P3")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("P4")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("Oz")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("Fz")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("T5")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("T6")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("F3")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("F4")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("T3")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("T4")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("C3")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("C4")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("Cz")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("O1")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("O2")); - mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("Pz")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("1")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("2")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("3")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("4")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("5")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("6")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("7")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("8")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("9")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("10")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("11")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("12")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("13")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("14")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("15")); + mElectrodes.Add(GetEEGElectrodes()->GetElectrodeByID("16")); } #endif diff --git a/src/Engine/Devices/OpenBCI/OpenBCIDevices.h b/src/Engine/Devices/OpenBCI/OpenBCIDevices.h index 07d7ef09..93d8a9da 100644 --- a/src/Engine/Devices/OpenBCI/OpenBCIDevices.h +++ b/src/Engine/Devices/OpenBCI/OpenBCIDevices.h @@ -87,6 +87,7 @@ class ENGINE_API OpenBCIDeviceBase : public BciDevice double GetExpectedJitter() const override { return 0.1; } bool IsWireless() const override { return true; } bool HasTestMode() const override { return false; } + double GetTimeoutLimit() const override { return 60; } void CreateSensors() override; @@ -133,8 +134,6 @@ class ENGINE_API OpenBCIDevice : public OpenBCIDeviceBase static const char* GetRuleName() { return "DEVICE_OpenBCI"; } double GetSampleRate() const override { return SAMPLERATECYTON; } - double GetTimeoutLimit() const override { return 60; } // Long timeout limit because channel config takes so long - void CreateElectrodes() override; inline double GetImpedance(uint32 idx) override { return mImpedances[idx].imp; } From 3c378ec5efef4a824760e53e499b24961ea00e46 Mon Sep 17 00:00:00 2001 From: Clint Banzhaf Date: Thu, 10 Aug 2023 08:17:41 +0200 Subject: [PATCH 4/6] Folder Upload (#305) --- src/QtBase/Backend/FilesCreateResponse.cpp | 20 +- src/QtBase/Backend/FilesCreateResponse.h | 2 + .../BackendFileSystemWidget.cpp | 475 +++++++++++++++--- .../BackendFileSystemWidget.h | 12 +- 4 files changed, 444 insertions(+), 65 deletions(-) diff --git a/src/QtBase/Backend/FilesCreateResponse.cpp b/src/QtBase/Backend/FilesCreateResponse.cpp index d6befed7..16ef5723 100644 --- a/src/QtBase/Backend/FilesCreateResponse.cpp +++ b/src/QtBase/Backend/FilesCreateResponse.cpp @@ -17,7 +17,21 @@ using namespace Core; // constructor FilesCreateResponse::FilesCreateResponse(QNetworkReply* reply) : Response(reply) { - // check for errors - if (mHasError == true) - return; + // data + Json::Item dataItem = mJson.Find("data"); + if (dataItem.IsNull() == true) + { + mHasError = true; + return; + } + + // get the data chunk id + Json::Item fileIdItem = dataItem.Find("fileId"); + if (fileIdItem.IsNull() == true) + { + mHasError = true; + return; + } + + mFileId = fileIdItem.GetString(); } diff --git a/src/QtBase/Backend/FilesCreateResponse.h b/src/QtBase/Backend/FilesCreateResponse.h index 5bc1a6e0..10cabb0b 100644 --- a/src/QtBase/Backend/FilesCreateResponse.h +++ b/src/QtBase/Backend/FilesCreateResponse.h @@ -17,6 +17,8 @@ class QTBASE_API FilesCreateResponse : public Response public: // constructor FilesCreateResponse(QNetworkReply* reply); + + const char* mFileId; }; diff --git a/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.cpp b/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.cpp index 87ae4f2e..059035e7 100644 --- a/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.cpp +++ b/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.cpp @@ -41,6 +41,8 @@ #include #include #include +#include +#include using namespace Core; @@ -303,7 +305,7 @@ void BackendFileSystemWidget::UpdateItems(QTreeWidgetItem* item) // reload file hiarchy from -void BackendFileSystemWidget::Refresh() +void BackendFileSystemWidget::Refresh(const QString& localfolder, const QString& cloudfolder, const bool xprun) { if (GetAuthenticationCenter()->IsInterfaceAllowed() == false) return; @@ -315,7 +317,7 @@ void BackendFileSystemWidget::Refresh() // 2. process request and connect to the reply QNetworkReply* reply = GetBackendInterface()->GetNetworkAccessManager()->ProcessRequest( request, Request::UIMODE_SILENT ); - connect(reply, &QNetworkReply::finished, this, [reply, this]() + connect(reply, &QNetworkReply::finished, this, [reply, this, localfolder, cloudfolder, xprun]() { QNetworkReply* networkReply = qobject_cast( sender() ); @@ -330,6 +332,10 @@ void BackendFileSystemWidget::Refresh() ReInit(); UpdateInterface(); + + // continue folder upload after folder creation (if any is pending) + if (!localfolder.isEmpty() && !cloudfolder.isEmpty()) + UploadFolder(localfolder, cloudfolder, xprun); }); } @@ -818,6 +824,14 @@ void BackendFileSystemWidget::OnContextMenu(const QPoint& point) QAction* saveToDiskAction = menu.addAction("Download", this, SLOT(OnSaveToDisk())); saveToDiskAction->setIcon(GetQtBaseManager()->FindIcon("Images/Icons/SaveAs.png")); } + + if (singleSelectedItem->GetCreud().Create() == true) + { + // upload JSON to cloud + QAction* uploadAction = menu.addAction("Upload", this, SLOT(OnUploadFromDisk())); + uploadAction->setIcon(GetQtBaseManager()->FindIcon("Images/Icons/Cloud.png")); + } + } else { @@ -856,10 +870,13 @@ void BackendFileSystemWidget::OnContextMenu(const QPoint& point) QAction* saveToDiskAction = menu.addAction("Download", this, SLOT(OnSaveToDisk()) ); saveToDiskAction->setIcon( GetQtBaseManager()->FindIcon("Images/Icons/SaveAs.png") ); + if (singleSelectedItem->GetCreud().Update() == true) + { // load JSON from disk and save it to the cloud - QAction* loadFromDiskSaveToCloudAction = menu.addAction("Upload local file to cloud", this, SLOT(OnLoadFromDiskAndSaveToCloud()) ); + QAction* loadFromDiskSaveToCloudAction = menu.addAction("Upload", this, SLOT(OnUploadFromDisk()) ); loadFromDiskSaveToCloudAction->setIcon( GetQtBaseManager()->FindIcon("Images/Icons/Cloud.png") ); - + } + menu.addSeparator(); // revisions @@ -1171,7 +1188,7 @@ void BackendFileSystemWidget::OnSaveToDisk() // download file FilesGetRequest request(GetUser()->GetToken(), rootModel.GetUuid()); QNetworkReply* reply = GetBackendInterface()->GetNetworkAccessManager()->ProcessRequest(request); - connect(reply, &QNetworkReply::finished, this, [reply, this, filename]() + connect(reply, &QNetworkReply::finished, this, [reply, this, filename, rootModel]() { Json json; QNetworkReply* networkReply = qobject_cast(sender()); @@ -1186,6 +1203,13 @@ void BackendFileSystemWidget::OnSaveToDisk() QMessageBox::warning(this, "Error", "JSON Parse failed", QMessageBox::Ok); return; } + + // add or update uuid + Core::String jsonstr; + auto jsonitm = json.GetRootItem().Find("uuid"); + if (!jsonitm.IsNull()) jsonitm.SetString(rootModel.GetUuid()); + else json.GetRootItem().AddString("uuid", rootModel.GetUuid()); + if (!json.WriteToFile(filename.toLatin1().data(), true)) { QMessageBox::warning(this, "Error", "File Write failed", QMessageBox::Ok); @@ -1254,7 +1278,7 @@ void BackendFileSystemWidget::OnSaveToDisk() const Core::String filename = path + model.GetNameString() + model.GetExtension(); FilesGetRequest request(GetUser()->GetToken(), model.GetUuid()); QNetworkReply* reply = GetBackendInterface()->GetNetworkAccessManager()->ProcessRequest(request); - connect(reply, &QNetworkReply::finished, this, [reply, this, filename]() + connect(reply, &QNetworkReply::finished, this, [reply, this, filename, model]() { Json json; QNetworkReply* networkReply = qobject_cast(sender()); @@ -1269,6 +1293,13 @@ void BackendFileSystemWidget::OnSaveToDisk() QMessageBox::warning(this, "Error", "JSON Parse failed", QMessageBox::Ok); return; } + + // add or update uuid + Core::String jsonstr; + auto jsonitm = json.GetRootItem().Find("uuid"); + if (!jsonitm.IsNull()) jsonitm.SetString(model.GetUuid()); + else json.GetRootItem().AddString("uuid", model.GetUuid()); + if (!json.WriteToFile(filename.AsChar(), true)) { QMessageBox::warning(this, "Error", "File Write failed", QMessageBox::Ok); @@ -1283,70 +1314,394 @@ void BackendFileSystemWidget::OnSaveToDisk() // load a JSON graph from disk and save it back to the backend -void BackendFileSystemWidget::OnLoadFromDiskAndSaveToCloud() +void BackendFileSystemWidget::OnUploadFromDisk() { - // get the filename where to save it - QFileDialog::Options options; - QString selectedFilter; - QString qtFilename = QFileDialog::getOpenFileName( this, // parent - "Open", // caption - "", // directory - "JSON (*.json)", - &selectedFilter, - options ); - - if (qtFilename.isEmpty() == true) - return; + const QList selectedItems = + mTreeWidget->selectedItems(); - String filename = FromQtString(qtFilename); + // only single selection supported + if (selectedItems.count() != 1) + return; - SelectionItem selectedItem = GetSelectedItem(); + QTreeWidgetItem* rootItem = selectedItems[0]; + SelectionItem rootModel = CreateSelectionItem(rootItem); - // is experience - if (selectedItem.GetTypeString().IsEqualNoCase(ITEM_TYPE_EXPERIENCE) == true) - { - Json jsonParser; - if (jsonParser.ParseFile(filename.AsChar()) == false) - { - LogError( "BackendFileSystemWidget::OnLoadFromDiskAndSaveToCloud(): Cannot open file '%s'.", filename.AsChar()); - return; - } + // upload single file + if (!rootModel.IsFolder()) + { + // show file selection dialog + const QString defaultName = mLastSelectedFileDialogFolder + '/' + rootModel.GetName() + rootModel.GetExtension().AsChar(); + const QString ext((rootModel.GetTypeString() + " (*" + rootModel.GetExtension() + ")").AsChar()); + const QString filename = QFileDialog::getOpenFileName(this, + "Upload", defaultName, ext); - Experience* experience = new Experience(); - if (experience->Load(jsonParser, jsonParser.GetRootItem()) == false) - { - LogError( "BackendFileSystemWidget::OnLoadFromDiskAndSaveToCloud(): Cannot parse Json file '%s'.", filename.AsChar()); - return; - } - - GetBackendInterface()->GetFileSystem()->SaveExperience( GetUser()->GetToken(), selectedItem.GetUuid(), experience ); + if (filename.isEmpty()) + return; - delete experience; - } - // is classifier/statemachine - else - { - Graph* graph = NULL; - - if (selectedItem.GetTypeString().IsEqualNoCase(ITEM_TYPE_CLASSIFIER) == true) - graph = new Classifier(); - else if (selectedItem.GetTypeString().IsEqualNoCase(ITEM_TYPE_STATEMACHINE) == true) - graph = new StateMachine(); - else - return; + // remember selected path + mLastSelectedFileDialogFolder = QFileInfo(filename).absolutePath(); - if (GraphImporter::LoadFromFile(filename.AsChar(), graph) == false) - { - LogError( "BackendFileSystemWidget::OnLoadFromDiskAndSaveToCloud(): Cannot parse Json file '%s'.", filename.AsChar()); - return; - } - - GetBackendInterface()->GetFileSystem()->SaveGraph( GetUser()->GetToken(), selectedItem.GetUuid(), graph ); + // load file + QFile f(filename); + if (!f.open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(this, "Error", "Failed to open file", QMessageBox::Ok); + return; + } + QTextStream in(&f); + QByteArray arr(f.readAll()); - delete graph; - } + // verify json + Json json; + if (!json.Parse(arr.constData())) { + QMessageBox::warning(this, "Error", "Failed to parse JSON", QMessageBox::Ok); + return; + } + + // replace a possible uuid with one from backend + Core::String jsonstr; + auto jsonitm = json.GetRootItem().Find("uuid"); + if (!jsonitm.IsNull()) + jsonitm.SetString(rootModel.GetUuid()); + json.WriteToString(jsonstr, true); + + // upload + FilesUpdateRequest request(GetUser()->GetToken(), rootModel.GetUuid(), jsonstr.AsChar()); + QNetworkReply* reply = GetBackendInterface()->GetNetworkAccessManager()->ProcessRequest(request); + connect(reply, &QNetworkReply::finished, this, [reply, this]() + { + QNetworkReply* networkReply = qobject_cast(sender()); + FilesUpdateResponse response(networkReply); + if (response.HasError()) + { + QMessageBox::warning(this, "Error", "Upload failed", QMessageBox::Ok); + return; + } + this->Refresh(); + }); + } + + // upload folder + else + { + // show folder selection dialog + const QString defaultName = mLastSelectedFileDialogFolder + '/' + rootModel.GetName(); + const QString folder = QFileDialog::getExistingDirectory( + this, "Select Folder", defaultName); + + if (folder.isEmpty()) + return; + + // remember selected path + mLastSelectedFileDialogFolder = QFileInfo(folder).absolutePath(); + + // shared root path of all elements + const Core::String& rootPath = rootModel.GetPathString(); + const uint32 rootPathLength = rootPath.GetLength(); + + // prepare upload + mPendingUploads = 0; + mLocalUploadRoot = folder; + mCloudUploadRoot = rootPath.AsChar(); + mLookup.clear(); + + // start upload + this->UploadFolder(folder, rootPath.AsChar(), false); + } +} + +void BackendFileSystemWidget::ReplaceUuid(Core::Json& json, const char* internalName) +{ + auto jsonattribs = json.GetRootItem().Find("attributes"); + if (jsonattribs.IsArray()) + { + uint32 numattribs = jsonattribs.Size(); + for (uint32 i = 0; i < numattribs; i++) + { + auto jsoninternalname = jsonattribs[i].Find("internalName"); + auto jsonvalue = jsonattribs[i].Find("value"); + if (jsoninternalname.IsString() && jsonvalue.IsString()) + { + auto* sname = jsoninternalname.GetString(); + auto* olduuid = jsonvalue.GetString(); + if (std::string(internalName) == sname) + { + std::string newuuid = mLookup[olduuid]; + //qDebug() << "old uuid: " << olduuid << " new uuid: " << newuuid.c_str(); + if (!newuuid.empty()) + jsonvalue.SetString(newuuid.c_str()); + } + } + } + } } +QTreeWidgetItem* BackendFileSystemWidget::FindItemByPath(const QString& path, const QString& type) +{ + QStringList l = path.split("/"); + if (l.length() == 0) + return 0; + const int toplvls = mTreeWidget->topLevelItemCount(); + for (int i = 0; i < toplvls; i++) + { + QTreeWidgetItem* item = mTreeWidget->topLevelItem(i); + QString p = item->data(0, USERDATA_PATH).toString(); + if (p.length() && p[p.length()-1] == '/') + p.chop(1); + if (l[0] != p) + continue; + if (l.length() == 1 && type == "folder") + return item; + for (int j = 1; j < l.length(); j++) + { + bool found = false; + for (int k = 0; k < item->childCount(); k++) + { + auto* child = item->child(k); + QString childtype = child->data(0, USERDATA_TYPE).toString(); + QString wantedtype = j < l.length()-1 ? "folder" : type; + if (child->text(0) == l[j] && childtype == wantedtype) + { + item = item->child(k); + found = true; + break; + } + } + if (!found) + return 0; + } + return item; + } + return 0; +} + +void BackendFileSystemWidget::UploadFolder(const QString& plocal, const QString& pcloud, const bool xprun) +{ + QString pathlocal = plocal; + QString pathcloud = pcloud; + pathlocal.replace("//", "/"); + pathcloud.replace("//", "/"); + if (pathlocal.length() && pathlocal[pathlocal.length()-1] == '/') pathlocal.chop(1); + if (pathcloud.length() && pathcloud[pathcloud.length()-1] == '/') pathcloud.chop(1); + //qDebug() << "local: " << pathlocal << " cloud: " << pathcloud << " xprun: " << xprun; + + // iterate folders + QDirIterator dirit(pathlocal, QStringList(), QDir::Dirs); + while (dirit.hasNext()) + { + QString p = dirit.next(); + QFileInfo finf(p); + QString basename = finf.baseName(); + QString cloudfolder; + + // filter for "." and ".." + if (basename.isEmpty()) + continue; + + // build path on cloud + cloudfolder = pathcloud + '/' + basename; + cloudfolder.replace("//", "/"); + + // exists + if (auto* item = FindItemByPath(cloudfolder, "folder")) + UploadFolder(p, cloudfolder, xprun); + + // must be created + else if (auto* item = FindItemByPath(pathcloud, "folder")) + { + //qDebug() << "creating: " << pathcloud << " (" << "folder" << ")"; + SelectionItem model = CreateSelectionItem(item); + if (!model.GetCreud().Create()) + continue; + mPendingUploads++; + FoldersCreateRequest request(GetUser()->GetToken(), basename.toLatin1().constData(), model.GetUuid()); + QNetworkReply* reply = GetBackendInterface()->GetNetworkAccessManager()->ProcessRequest( request ); + connect(reply, &QNetworkReply::finished, this, [reply, this, p, cloudfolder, xprun]() + { + QNetworkReply* networkReply = qobject_cast( sender() ); + FoldersCreateResponse response(networkReply); + mPendingUploads--; + if (!response.HasError()) + this->Refresh(p, cloudfolder, xprun); + if (mPendingUploads == 0 && !xprun) { + UploadFolder(mLocalUploadRoot, mCloudUploadRoot, true); + } + }); + } + else + assert(false); + } + + // filter for types + QStringList filter; + if (xprun) { + filter << "*.xp.json"; // experience + } + else { + filter << "*.cs.json"; // classifier + filter << "*.sm.json"; // statemachine + } + + // iterate files + QDirIterator filesit(pathlocal, filter, QDir::Files); + while (filesit.hasNext()) + { + QString f = filesit.next(); + + // load file + QFile file(f); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(this, "Error", "Failed to open file", QMessageBox::Ok); + continue; + } + QTextStream in(&file); + QByteArray arr(file.readAll()); + + // verify json + Json json; + if (!json.Parse(arr.constData())) { + QMessageBox::warning(this, "Error", "Failed to parse JSON", QMessageBox::Ok); + continue; + } + + // get file extension and map to type + QFileInfo finf(f); + QString suff = finf.completeSuffix(); + QString basename = finf.baseName(); + QString type = + suff == "cs.json" ? "CLASSIFIER" : + suff == "sm.json" ? "STATEMACHINE" : + suff == "xp.json" ? "EXPERIENCE" : "folder"; + + // remove file extension with . + f.chop(suff.length()+1); + + // build path on cloud + f.remove(pathlocal); + f = pathcloud + '/' + f; + f.replace("//", "/"); + + // update existing file + if (auto* item = FindItemByPath(f, type)) + { + //qDebug() << "updating: " << f << " (" << type << ")"; + SelectionItem fileModel = CreateSelectionItem(item); + if (!fileModel.GetCreud().Update()) + continue; + + // try to load old uuid, store in map and replace with new + auto jsonuuid = json.GetRootItem().Find("uuid"); + if (jsonuuid.IsString()) + { + mLookup[jsonuuid.GetString()] = fileModel.GetUuid(); + jsonuuid.SetString(fileModel.GetUuid()); + } + + // replace linked uuids for classifier and statemachine if known + if (type == "EXPERIENCE") + { + ReplaceUuid(json, "classifierUuid"); + ReplaceUuid(json, "stateMachineUuid"); + } + + // serialize + Core::String jsonstr; + json.WriteToString(jsonstr, true); + + // start update request + mPendingUploads++; + FilesUpdateRequest request(GetUser()->GetToken(), fileModel.GetUuid(), jsonstr.AsChar()); + QNetworkReply* reply = GetBackendInterface()->GetNetworkAccessManager()->ProcessRequest(request); + connect(reply, &QNetworkReply::finished, this, [reply, this, xprun]() + { + QNetworkReply* networkReply = qobject_cast(sender()); + FilesUpdateResponse response(networkReply); + mPendingUploads--; + if (mPendingUploads == 0) { + if (xprun) this->Refresh(); + else UploadFolder(mLocalUploadRoot, mCloudUploadRoot, true); + } + if (response.HasError()) + { + QMessageBox::warning(this, "Error", "Upload failed", QMessageBox::Ok); + return; + } + }); + } + + // create new file + else if (auto* item = FindItemByPath(pathcloud, "folder")) + { + //qDebug() << "creating: " << f << " (" << type << ")"; + SelectionItem folderModel = CreateSelectionItem(item); + if (!folderModel.GetCreud().Create()) + continue; + + // create empty file first + FilesCreateRequest request( + GetUser()->GetToken(), + basename.toLatin1().constData(), + folderModel.GetUuid(), + type.toLatin1().constData(), + "{}"); + + mPendingUploads++; + QNetworkReply* reply = GetBackendInterface()->GetNetworkAccessManager()->ProcessRequest(request); + connect(reply, &QNetworkReply::finished, this, [reply, this, json, xprun, type]() + { + QNetworkReply* networkReply = qobject_cast(sender()); + FilesCreateResponse response(networkReply); + mPendingUploads--; + + if (response.HasError()) + return; + + // try to load old uuid, store in map and replace with new + auto jsonitm = json.GetRootItem().Find("uuid"); + if (!jsonitm.IsNull()) + { + mLookup[jsonitm.GetString()] = response.mFileId; + jsonitm.SetString(response.mFileId); + } + + Json json2(json); + + // replace linked uuids for classifier and statemachine if known + if (type == "EXPERIENCE") + { + ReplaceUuid(json2, "classifierUuid"); + ReplaceUuid(json2, "stateMachineUuid"); + } + + // serialize + Core::String jsonstr; + json2.WriteToString(jsonstr, true); + + // update json content on backend + mPendingUploads++; + FilesUpdateRequest request(GetUser()->GetToken(), response.mFileId, jsonstr.AsChar()); + QNetworkReply* reply = GetBackendInterface()->GetNetworkAccessManager()->ProcessRequest(request); + connect(reply, &QNetworkReply::finished, this, [reply, this, xprun]() + { + QNetworkReply* networkReply = qobject_cast(sender()); + FilesUpdateResponse response(networkReply); + mPendingUploads--; + if (mPendingUploads == 0) { + if (xprun) this->Refresh(); + else UploadFolder(mLocalUploadRoot, mCloudUploadRoot, true); + } + if (response.HasError()) + { + QMessageBox::warning(this, "Error", "Upload failed", QMessageBox::Ok); + return; + } + }); + }); + } + else + assert(false); + } + //qDebug() << "Pending: " << std::to_string(mPendingUploads).c_str(); +} // called when the minus button got clicked void BackendFileSystemWidget::OnRemoveItem() diff --git a/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.h b/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.h index 41bbf629..b47c8543 100644 --- a/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.h +++ b/src/Studio/Plugins/BackendFileSystem/BackendFileSystemWidget.h @@ -155,7 +155,7 @@ class BackendFileSystemWidget : public QWidget bool ExpandByPath(const QStringList& itemPath); public slots: - void Refresh(); + void Refresh(const QString& localfolder = "", const QString& cloudfolder = "", const bool xprun = false); void OnCreateFile(); void OnCreateFolder(); void OnSearchFieldTextEdited(const QString & text); @@ -167,7 +167,7 @@ class BackendFileSystemWidget : public QWidget void OnCopyFileToPersonalFolder(); void OnContextMenuRetrieveItemRevision(); - void OnLoadFromDiskAndSaveToCloud(); + void OnUploadFromDisk(); void OnCopyJsonToClipboard(); void OnSaveToDisk(); @@ -237,6 +237,10 @@ class BackendFileSystemWidget : public QWidget CollapseState* FindCollapsedState(const char* uuid); bool IsItemCollapsed(const char* uuid); + void ReplaceUuid(Core::Json& json, const char* internalName); + QTreeWidgetItem* FindItemByPath(const QString& path, const QString& type); + void UploadFolder(const QString& pathlocal, const QString& pathcloud, const bool xprun); + Core::Array mSelectedItems; Core::Array mFolderCollapseStates; @@ -256,6 +260,10 @@ class BackendFileSystemWidget : public QWidget ImageButton* mRefreshButton; SearchBoxWidget* mSearchBox; + size_t mPendingUploads; + std::map mLookup; + QString mLocalUploadRoot; + QString mCloudUploadRoot; }; From 71acaf60d8c78a8772b4eca31a31768f039adfda Mon Sep 17 00:00:00 2001 From: sedrakSofthenge <148331189+sedrakSofthenge@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:31:18 +0400 Subject: [PATCH 5/6] Spectrogram2d, View and Logging (#324) * Added functionality to draw spectrogram charts horizontally per need Added way to draw fixed lenght charts in Signal View tab when specified with input Adjusted logic of logging to save logs into logs folder with timestamp on each file name * Fixed SignalView not properly fixing length issue * Changed selection of checkboxes for electrodes from layout to selector node * Changed chart drawing direction to horizontal from vertical in horizontal view * Updated experienceplayer.layout file --- src/Engine/Graph/ChannelSelectorNode.cpp | 1 - src/Engine/Graph/ViewNode.cpp | 51 +- src/Engine/Graph/ViewNode.h | 14 +- src/QtBase/AttributeWidgets/PropertyManager.h | 2 + src/QtBase/PluginSystem/Plugin.h | 2 +- .../Resources/Layouts/ExperiencePlayer.layout | 180 ++++--- src/Studio/AppManager.cpp | 5 +- src/Studio/AppManager.h | 4 +- .../Spectrogram2D/Spectrogram2DPlugin.cpp | 68 ++- .../Spectrogram2D/Spectrogram2DPlugin.h | 7 + .../Spectrogram2D/Spectrogram2DWidget.cpp | 63 ++- .../Spectrogram2D/Spectrogram2DWidget.h | 4 + src/Studio/Plugins/View/ViewPlugin.cpp | 15 + src/Studio/Plugins/View/ViewPlugin.h | 1 + src/Studio/Plugins/View/ViewWidget.cpp | 25 +- src/Studio/Rendering/OpenGLWidget.cpp | 82 +++- src/Studio/Rendering/OpenGLWidget.h | 3 +- .../Rendering/OpenGLWidget2DHelpers.cpp | 453 ++++++++++++------ src/Studio/Rendering/OpenGLWidget2DHelpers.h | 11 +- .../Widgets/ChannelMultiSelectionWidget.cpp | 4 +- 20 files changed, 695 insertions(+), 300 deletions(-) diff --git a/src/Engine/Graph/ChannelSelectorNode.cpp b/src/Engine/Graph/ChannelSelectorNode.cpp index 7bac7ede..cc9970f6 100644 --- a/src/Engine/Graph/ChannelSelectorNode.cpp +++ b/src/Engine/Graph/ChannelSelectorNode.cpp @@ -297,7 +297,6 @@ bool ChannelSelectorNode::IsChannelSelected(const ChannelBase* channel) const return false; } - // create the output ports (with empty multi channels) void ChannelSelectorNode::ReInitOutputPorts() { diff --git a/src/Engine/Graph/ViewNode.cpp b/src/Engine/Graph/ViewNode.cpp index 798a7e98..f5a3ea22 100644 --- a/src/Engine/Graph/ViewNode.cpp +++ b/src/Engine/Graph/ViewNode.cpp @@ -30,7 +30,6 @@ #include "../DSP/Spectrum.h" #include "../EngineManager.h" - using namespace Core; // constructor @@ -60,10 +59,11 @@ void ViewNode::Init() // SETUP PORTS // setup the input ports - InitInputPorts(3); + InitInputPorts(4); GetInputPort(INPUTPORT_DOUBLE).Setup("In", "x1", AttributeChannels::TYPE_ID, INPUTPORT_DOUBLE); GetInputPort(INPUTPORT_SPECTRUM).Setup("In (Spectrum)", "x2", AttributeChannels::TYPE_ID, INPUTPORT_SPECTRUM); GetInputPort(INPUTPORT_ENABLE).Setup("Enable", "enable", AttributeChannels::TYPE_ID, INPUTPORT_ENABLE); + GetInputPort(INPUTPORT_LENGTH).Setup("Length in mins", "length", AttributeChannels::TYPE_ID, INPUTPORT_LENGTH); // ATTRIBUTES @@ -100,6 +100,9 @@ void ViewNode::Init() AttributeSettings* attributeCombinedView = RegisterAttribute("Combined View", "combineView", "Display all input channels in a single chart.", ATTRIBUTE_INTERFACETYPE_CHECKBOX); attributeCombinedView->SetDefaultValue( AttributeBool::Create(defaultCombinedView) ); + AttributeSettings* fixedLength = RegisterAttribute( "Fixed Length in mins", "fixedLength", "Fix length in Seconds.", Core::ATTRIBUTE_INTERFACETYPE_CHECKBOX); + fixedLength->SetDefaultValue( AttributeBool::Create(false) ); + // sync buffer size to initial value SyncBufferSize(true); } @@ -145,6 +148,32 @@ void ViewNode::Update(const Time& elapsed, const Time& delta) } } + auto fixedLengthCache = mFixedLengthDuration; + InputPort& lengthPort = GetInputPort(INPUTPORT_LENGTH); + if (lengthPort.HasConnection() == false) + { + GetAttributeSettings(ATTRIB_LENGTH)->SetIsEnabled(true); + mFixedLengthDuration = -1.; + } + else + { + if (lengthPort.GetChannels()->GetNumChannels() > 0 && lengthPort.GetChannels()->GetChannel(0)->IsEmpty() == false) + { + GetAttributeSettings(ATTRIB_LENGTH)->SetIsEnabled(false); + const double sampleValue = lengthPort.GetChannels()->GetChannel(0)->AsType()->GetLastSample(); + if (sampleValue > 0.) + mFixedLengthDuration = sampleValue; + } + else + { + mFixedLengthDuration = -1.; + } + } + + // If the duration is changed, update buffer size + if (abs(fixedLengthCache - mFixedLengthDuration) > 0.0001) + SyncBufferSize(false); + // update scaling range (i.e. the auto modes need to recalculate the range) CalculateScalingRange(); @@ -195,6 +224,12 @@ void ViewNode::OnAttributesChanged() // show/hide color picker GetAttributeSettings(ATTRIB_COLORPICK)->SetVisible( CustomColor() ); + InputPort &lengthPort = GetInputPort(INPUTPORT_LENGTH); + if (FixedLength()) + lengthPort.SetVisible(true); + else + lengthPort.SetVisible(false); + EMIT_EVENT( OnAttributeUpdated(mParentGraph, this, GetAttributeValue(ATTRIB_COLORPICK)) ); } @@ -299,7 +334,7 @@ void ViewNode::CalculateScalingRange() for (uint32 i = 0; i < numChannels; ++i) { Channel* channel = GetDoubleChannel(i); - const uint32 numSamples = mViewDuration * channel->GetSampleRate(); + const uint32 numSamples = GetViewDuration() * channel->GetSampleRate(); Epoch epoch(channel, numSamples); epoch.SetPositionByOffset(0); @@ -315,7 +350,7 @@ void ViewNode::CalculateScalingRange() for (uint32 i = 0; i < numChannels; ++i) { Channel* channel = GetDoubleChannel(i); - const uint32 numSamples = mViewDuration * channel->GetSampleRate(); + const uint32 numSamples = GetViewDuration() * channel->GetSampleRate(); Epoch epoch(channel, numSamples); epoch.SetPositionByOffset(0); @@ -341,14 +376,16 @@ void ViewNode::SyncBufferSize(bool discard) InputPort& doublePort = GetInputPort(INPUTPORT_DOUBLE); InputPort& specPort = GetInputPort(INPUTPORT_SPECTRUM); InputPort& enablePort = GetInputPort(INPUTPORT_ENABLE); + InputPort& lengthPort = GetInputPort(INPUTPORT_LENGTH); MultiChannel* channels; + auto duration = GetViewDuration(); if (doublePort.HasConnection() && (channels = doublePort.GetChannels())) - channels->SetBufferSizeInSeconds(mViewDuration, discard); + channels->SetBufferSizeInSeconds(duration, discard); if (specPort.HasConnection() && (channels = specPort.GetChannels())) - channels->SetBufferSizeInSeconds(mViewDuration, discard); + channels->SetBufferSizeInSeconds(duration, discard); if (enablePort.HasConnection() && (channels = enablePort.GetChannels())) - channels->SetBufferSizeInSeconds(mViewDuration, discard); + channels->SetBufferSizeInSeconds(duration, discard); } diff --git a/src/Engine/Graph/ViewNode.h b/src/Engine/Graph/ViewNode.h index e8cca916..9fbfa7ed 100644 --- a/src/Engine/Graph/ViewNode.h +++ b/src/Engine/Graph/ViewNode.h @@ -45,6 +45,7 @@ class ENGINE_API ViewNode : public SPNode INPUTPORT_DOUBLE = 0, INPUTPORT_SPECTRUM, INPUTPORT_ENABLE, + INPUTPORT_LENGTH, }; enum @@ -55,6 +56,7 @@ class ENGINE_API ViewNode : public SPNode ATTRIB_USECOLOR, ATTRIB_COLORPICK, ATTRIB_COMBINE, + ATTRIB_LENGTH, }; enum EScalingMode @@ -94,6 +96,9 @@ class ENGINE_API ViewNode : public SPNode double GetRangeMin(uint32 index) const { if (index < mRangesMin.Size()) return mRangesMin[index]; return 0.0; } double GetRangeMax(uint32 index) const { if (index < mRangesMax.Size()) return mRangesMax[index]; return 0.0; } + bool FixedLength() const { return GetBoolAttribute(ATTRIB_LENGTH); } // checks if length is fixed + double GetFixedLength() const { return mFixedLengthDuration; } // returns the length value + // get the enable flag bool GetEnableValue() const { return mEnableValue; } @@ -108,7 +113,7 @@ class ENGINE_API ViewNode : public SPNode Channel* GetSpectrumChannel(uint32 index); inline void SetViewDuration(double seconds) - { + { mViewDuration = seconds; SyncBufferSize(false); } @@ -118,6 +123,12 @@ class ENGINE_API ViewNode : public SPNode private: void CalculateScalingRange(); void SyncBufferSize(bool discard = false); + inline double GetViewDuration() + { + if (mFixedLengthDuration >= 0.) + return mFixedLengthDuration * 60.; // since this is in mins + return mViewDuration; + } double mViewDuration; double mMaxViewDuration; @@ -126,6 +137,7 @@ class ENGINE_API ViewNode : public SPNode Core::Array mRangesMax; bool mEnableValue; + double mFixedLengthDuration; }; diff --git a/src/QtBase/AttributeWidgets/PropertyManager.h b/src/QtBase/AttributeWidgets/PropertyManager.h index 915696f0..4f01f08a 100644 --- a/src/QtBase/AttributeWidgets/PropertyManager.h +++ b/src/QtBase/AttributeWidgets/PropertyManager.h @@ -59,6 +59,8 @@ class QTBASE_API PropertyManager : public QObject // manual property adding Property* AddProperty(const char* groupName, const char* propertyName, AttributeWidget* attributeWidget, Core::Attribute* attributeValue, Core::AttributeSettings* settings, bool autoDelete=true, bool emitSignal=false); + Core::Array GetProperties() const { return mProperties; } + signals: void ValueChanged(Property* property); void PropertyAdded(const char* groupName, Property* property); diff --git a/src/QtBase/PluginSystem/Plugin.h b/src/QtBase/PluginSystem/Plugin.h index 6436fc79..bb5c6dba 100644 --- a/src/QtBase/PluginSystem/Plugin.h +++ b/src/QtBase/PluginSystem/Plugin.h @@ -91,7 +91,7 @@ class QTBASE_API Plugin : public QObject, public Core::AttributeSet void CreateBaseInterface(const char* objectName); inline DockWidget* GetDockWidget() { return mDock; } - + // attributes & settings void SaveSettings(Core::Json& json, Core::Json::Item& pluginItem) { AttributeSet::Write( json, pluginItem, true ); } bool LoadSettings(const Core::Json& json, const Core::Json::Item& item) { return AttributeSet::Read(json, item, true); } diff --git a/src/QtBase/Resources/Layouts/ExperiencePlayer.layout b/src/QtBase/Resources/Layouts/ExperiencePlayer.layout index 13382f88..c294f973 100644 --- a/src/QtBase/Resources/Layouts/ExperiencePlayer.layout +++ b/src/QtBase/Resources/Layouts/ExperiencePlayer.layout @@ -1,78 +1,104 @@ -{ - "versionHigh": 1, - "versionLow": 0, - "compileDate": "Jul 11 2023", - "mainWindow": { - "posX": 0, - "posY": 0, - "width": 1920, - "height": 1080, - "isMaximized": true, - "geometry": "AdnQywADAAD/////////+AAACgAAAAVwAAAA3AAAAIEAAAbkAAAD5wAAAAACAAAACgAAAAAAAAAAFwAACf8AAAVv", - "state": "AAAA/wAAAAD9AAAAAQAAAAIAAAoAAAAFQ/wBAAAAAvwAAAAAAAABXQAAASkA/////AIAAAAI+wAAACoAUABMAFUARwBJAE4AMQA3ADQAOQA5ADIAOQA2ADIAMQAxADcANwA1ADUBAAAAFQAAAmcAAAAAAAAAAPsAAAAoAFAATABVAEcASQBOADEAMgA4ADUAOQA5ADkANwA2ADIAMgAwADQAOAAAAAAUAAABRwAAAAAAAAAA/AAAABYAAAOIAAABXAD////6AAAAAAEAAAAC+wAAAEgAMABjAGIAOABjADAAMwAyAC0AMgBlADQANgAtADQANgBjAGIALQA4ADkAYgA0AC0ANwAzADEAYwA2ADMAMQBhADAANgA5AGQBAAAAAP////8AAAEpAP////sAAABIADcAOQAzAGMANwBlADQAMgAtADIAOQBiAGUALQA0ADkAMwAzAC0AYQA2AGEAOQAtAGIAYwA3AGIAMgAyAGUAYgA0AGQAMwAzAQAAAAAAAAFyAAAAAAAAAAD7AAAASAAxADIANgBjADgAZgBjADMALQA4ADkAMwA1AC0ANAA5ADAAYwAtAGIAZQA3ADkALQA2ADMAZAA2ADMAZQAxADAAMwAyADgAMgAAAAMQAAABLQAAAAAAAAAA+wAAACoAUABMAFUARwBJAE4AMQA1ADUAMwAxADMAMQAyADUANwAxADYAMAA2ADgBAAADogAAAbcAAACqAP////sAAAAoAFAATABVAEcASQBOADEAOAAwADIAMwAyADQAOQA3ADYAMgA4ADkAOQAAAAK4AAABTwAAAAAAAAAA+wAAACgAUABMAFUARwBJAE4ANwAzADkAMwAzADAAOAAzADQAMQA3ADEAMQA4AAAAAYYAAAGDAAAAAAAAAAD7AAAAKABQAEwAVQBHAEkATgAxADkANgAwADEAOQAzADgAOQAxADYANQAzADIAAAACnwAAAWQAAAAAAAAAAPwAAAFhAAAInwAAAQkA/////AEAAAAD/AAAAWEAAAdYAAAAgQD////8AgAAAAb7AAAAKABQAEwAVQBHAEkATgAyADMAOQAzADAANQA3ADQAMAAxADcANQA4ADgAAAAAFgAAAKQAAAAAAAAAAPsAAABIADMAMQA5ADkAMAA5ADUAYgAtADgAMAA0ADYALQA0AGEAZQA4AC0AOAA2AGMAMgAtAGQAMwA2AGQAMAA5AGIAOQBiADMAMQBlAAAAABYAAAJ1AAAAAAAAAAD7AAAASABkADYAMQBlADUAYgAwAGEALQBmAGYAMAA1AC0ANAA1ADQAMwAtAGEANQBiADEALQA1ADAAMQBkADYAYQAwADIAMQAwAGYANQEAAAAWAAAB0AAAAFAA////+wAAAEgAOABmADUAOQBhADMAZgBlAC0AZQA2ADYAZAAtADQAYgA4ADYALQA5AGYANQBlAC0AMgBkAGYAYgA2ADUAOQA1AGIAZQBjADABAAAB6gAAA28AAABkAP////sAAAAoAFAATABVAEcASQBOADQAMQAwADMAMQA0ADEANwAyADEAMAA2ADUANwEAAAFQAAADIQAAAAAAAAAA/AAAAUYAAAMrAAAAAAD////6AAAAAAEAAAAF+wAAACYAUABMAFUARwBJAE4AMQA5ADEANAA3ADcANgAzADEANAAwADgAOAEAAAAA/////wAAAAAAAAAA+wAAACoAUABMAFUARwBJAE4AMgAyADQANwA3ADIAMwA1ADgAMgAxADAANAA4ADEAAAAAAP////8AAAAAAAAAAPsAAAAmAFAATABVAEcASQBOADEAMAA0ADIAMgA2ADEANwA0ADgANQA5ADAAAAAAAP////8AAAAAAAAAAPsAAAAqAFAATABVAEcASQBOADIANAA3ADYAMwAxADAAOAAxADkAMwAyADIANAA4AAAAAAD/////AAAAAAAAAAD7AAAAKgBQAEwAVQBHAEkATgAzADAAMwAwADUAMwAyADUAOAAyADMAMgA2ADcANwAAAAGdAAAF4wAAAAAAAAAA/AAACL0AAAFDAAAAhAD////8AgAAAAr7AAAANABHAHIAYQBwAGgAUABsAHUAZwBpAG4AOgA6AFAAYQBsAGUAdAB0AGUAVwBpAGQAZwBlAHQBAAAByQAAAH4AAAAAAAAAAPsAAAA6AEcAcgBhAHAAaABQAGwAdQBnAGkAbgA6ADoAQQB0AHQAcgBpAGIAdQB0AGUAcwBXAGkAZABnAGUAdAEAAAJLAAAApQAAAAAAAAAA/AAAABYAAAVDAAAAXAD////8AQAAAAL7AAAAKgBQAEwAVQBHAEkATgAyADEANgAxADYAMgAwADIANAA3ADEAMwA0ADQANQAAAAYjAAAAvwAAAAAAAAAA+wAAAEgAOABhAGUAYgBlADMANQBiAC0ANgA2AGUAMQAtADQAYwBkADAALQA4ADMANQBhAC0AZgBkADMANgAzADcAZQA0ADMAMgA1AGEBAAAIvQAAAUMAAACEAP////sAAAA2AEcAcgBhAHAAaABQAGwAdQBnAGkAbgA6ADoAUgBlAHMAbwB1AHIAYwBlAFcAaQBkAGcAZQB0AQAAA5cAAADaAAAAAAAAAAD7AAAAKgBQAEwAVQBHAEkATgAyADEANgAzADEAMwAwADUAMQAyADIAOQA0ADQAMQEAAAI5AAAAdgAAAAAAAAAA+wAAAD4AQwBsAGEAcwBzAGkAZgBpAGUAcgBQAGwAdQBnAGkAbgA6ADoAUABhAGwAZQB0AHQAZQBXAGkAZABnAGUAdAEAAAJUAAAAggAAAAAAAAAA+wAAAEQAQwBsAGEAcwBzAGkAZgBpAGUAcgBQAGwAdQBnAGkAbgA6ADoAQQB0AHQAcgBpAGIAdQB0AGUAcwBXAGkAZABnAGUAdAEAAALaAAAAxAAAAAAAAAAA+wAAAEAAQwBsAGEAcwBzAGkAZgBpAGUAcgBQAGwAdQBnAGkAbgA6ADoAUgBlAHMAbwB1AHIAYwBlAFcAaQBkAGcAZQB0AQAAA6IAAADPAAAAAAAAAAD7AAAAOABDAGwAYQBzAHMAaQBmAGkAZQByAFAAbAB1AGcAaQBuADoAOgBBAHQAdAByAGkAYgB1AHQAZQBzAQAAAZgAAAFJAAAAAAAAAAD7AAAANgBDAGwAYQBzAHMAaQBmAGkAZQByAFAAbAB1AGcAaQBuADoAOgBSAGUAcwBvAHUAcgBjAGUAcwEAAALlAAAA9gAAAAAAAAAA+wAAACgAUABMAFUARwBJAE4AMQA3ADYAMAAxADkANgA5ADMAMQA3ADcAMQA5AgAABJwAAAA9AAAB9AAAAkIAAAoAAAAAAAAAAAQAAAAEAAAACAAAAAj8AAAAAA==" - }, - "plugins": [ - { - "objectName": "PLUGIN155313125716068", - "typeUuid": "c38f75d4-7eca-11e4-b4a9-0800200c9a66", - "attributes": [ - { - "internalName": "settingsWindowVisibility", - "value": false - } - ] - }, - { - "objectName": "0cb8c032-2e46-46cb-89b4-731c631a069d", - "typeUuid": "c38f75e1-7eca-11e4-b4a9-0800200c9a66", - "attributes": [ - { - "internalName": "settingsWindowVisibility", - "value": false - } - ] - }, - { - "objectName": "8aebe35b-66e1-4cd0-835a-fd3637e4325a", - "typeUuid": "a9f94048-a277-11e5-bf7f-feff819cdc9f", - "attributes": [ - { - "internalName": "settingsWindowVisibility", - "value": false - } - ] - }, - { - "objectName": "d61e5b0a-ff05-4543-a5b1-501d6a0210f5", - "typeUuid": "1aea9056-26e3-11e5-b345-feff819cdc9f", - "attributes": [ - { - "internalName": "settingsWindowVisibility", - "value": false - } - ] - }, - { - "objectName": "8f59a3fe-e66d-4b86-9f5e-2dfb6595bec0", - "typeUuid": "30ca8a46-321b-11e5-a151-feff819cdc9f", - "attributes": [ - { - "internalName": "settingsWindowVisibility", - "value": false - }, - { - "internalName": "timeRange", - "value": 10.0 - }, - { - "internalName": "style", - "value": 4 - }, - { - "internalName": "showLatencyMarker", - "value": false - } - ] - } - ] +{ + "versionHigh": 1, + "versionLow": 0, + "compileDate": "Feb 7 2025", + "mainWindow": { + "posX": -1, + "posY": -9, + "width": 1920, + "height": 991, + "isMaximized": true, + "geometry": "AdnQywADAAD/////////9wAAB4AAAAP8AAAAyAAAAPgAAAZWAAAC8AAAAAACAAAAB4AAAAAAAAAAHQAAB38AAAP7", + "state": "AAAA/wAAAAD9AAAAAQAAAAIAAAeAAAADx/wBAAAAAvwAAAAAAAABzgAAAb8A/////AIAAAAJ+wAAACoAUABMAFUARwBJAE4AMQA3ADQAOQA5ADIAOQA2ADIAMQAxADcANwA1ADUBAAAAFQAAAmcAAAAAAAAAAPsAAAAoAFAATABVAEcASQBOADEAMgA4ADUAOQA5ADkANwA2ADIAMgAwADQAOAAAAAAUAAABRwAAAAAAAAAA/AAAABgAAAGEAAABLgD////6AAAAAAEAAAAC+wAAAEgAMABjAGIAOABjADAAMwAyAC0AMgBlADQANgAtADQANgBjAGIALQA4ADkAYgA0AC0ANwAzADEAYwA2ADMAMQBhADAANgA5AGQBAAAAAP////8AAAG/AP////sAAABIADcAOQAzAGMANwBlADQAMgAtADIAOQBiAGUALQA0ADkAMwAzAC0AYQA2AGEAOQAtAGIAYwA3AGIAMgAyAGUAYgA0AGQAMwAzAQAAAAAAAAFyAAAAAAAAAAD7AAAASABkADYAMQBlADUAYgAwAGEALQBmAGYAMAA1AC0ANAA1ADQAMwAtAGEANQBiADEALQA1ADAAMQBkADYAYQAwADIAMQAwAGYANQEAAAGhAAABTAAAAFAA////+wAAAEgAMQAyADYAYwA4AGYAYwAzAC0AOAA5ADMANQAtADQAOQAwAGMALQBiAGUANwA5AC0ANgAzAGQANgAzAGUAMQAwADMAMgA4ADIAAAADEAAAAS0AAAAAAAAAAPsAAAAqAFAATABVAEcASQBOADEANQA1ADMAMQAzADEAMgA1ADcAMQA2ADAANgA4AQAAAvIAAADtAAAArgD////7AAAAKABQAEwAVQBHAEkATgAxADgAMAAyADMAMgA0ADkANwA2ADIAOAA5ADkAAAACuAAAAU8AAAAAAAAAAPsAAAAoAFAATABVAEcASQBOADcAMwA5ADMAMwAwADgAMwA0ADEANwAxADEAOAAAAAGGAAABgwAAAAAAAAAA+wAAACgAUABMAFUARwBJAE4AMQA5ADYAMAAxADkAMwA4ADkAMQA2ADUAMwAyAAAAAp8AAAFkAAAAAAAAAAD8AAAB0wAABa0AAALdAP////wBAAAAA/wAAAHTAAAEKAAAAlQA/////AIAAAAG+wAAACgAUABMAFUARwBJAE4AMgAzADkAMwAwADUANwA0ADAAMQA3ADUAOAA4AAAAABYAAACkAAAAAAAAAAD7AAAASAAzADEAOQA5ADAAOQA1AGIALQA4ADAANAA2AC0ANABhAGUAOAAtADgANgBjADIALQBkADMANgBkADAAOQBiADkAYgAzADEAZQAAAAAWAAACdQAAAAAAAAAA+wAAAEgAOABmADUAOQBhADMAZgBlAC0AZQA2ADYAZAAtADQAYgA4ADYALQA5AGYANQBlAC0AMgBkAGYAYgA2ADUAOQA1AGIAZQBjADABAAAAGAAAAecAAABkAP////sAAABIADQAYwBjADcAOQA0AGYAZQAtADQAYwAxADEALQA0AGUAYgAxAC0AYgA5AGQAZgAtAGEAYgBiADMANwA3ADUAZgA5ADQAMAA3AQAAAgQAAAHbAAAAzAD////7AAAAKABQAEwAVQBHAEkATgA0ADEAMAAzADEANAAxADcAMgAxADAANgA1ADcBAAABUAAAAyEAAAAAAAAAAPwAAAFGAAADKwAAAAAA////+gAAAAABAAAABfsAAAAmAFAATABVAEcASQBOADEAOQAxADQANwA3ADYAMwAxADQAMAA4ADgBAAAAAP////8AAAAAAAAAAPsAAAAqAFAATABVAEcASQBOADIAMgA0ADcANwAyADMANQA4ADIAMQAwADQAOAAxAAAAAAD/////AAAAAAAAAAD7AAAAJgBQAEwAVQBHAEkATgAxADAANAAyADIANgAxADcANAA4ADUAOQAwAAAAAAD/////AAAAAAAAAAD7AAAAKgBQAEwAVQBHAEkATgAyADQANwA2ADMAMQAwADgAMQA5ADMAMgAyADQAOAAAAAAA/////wAAAAAAAAAA+wAAACoAUABMAFUARwBJAE4AMwAwADMAMAA1ADMAMgA1ADgAMgAzADIANgA3ADcAAAABnQAABeMAAAAAAAAAAPwAAAYAAAABgAAAAIQA/////AIAAAAK+wAAADQARwByAGEAcABoAFAAbAB1AGcAaQBuADoAOgBQAGEAbABlAHQAdABlAFcAaQBkAGcAZQB0AQAAAckAAAB+AAAAAAAAAAD7AAAAOgBHAHIAYQBwAGgAUABsAHUAZwBpAG4AOgA6AEEAdAB0AHIAaQBiAHUAdABlAHMAVwBpAGQAZwBlAHQBAAACSwAAAKUAAAAAAAAAAPwAAAAYAAADxwAAAFwA/////AEAAAAC+wAAACoAUABMAFUARwBJAE4AMgAxADYAMQA2ADIAMAAyADQANwAxADMANAA0ADUAAAAGIwAAAL8AAAAAAAAAAPsAAABIADgAYQBlAGIAZQAzADUAYgAtADYANgBlADEALQA0AGMAZAAwAC0AOAAzADUAYQAtAGYAZAAzADYAMwA3AGUANAAzADIANQBhAQAABgAAAAGAAAAAhAD////7AAAANgBHAHIAYQBwAGgAUABsAHUAZwBpAG4AOgA6AFIAZQBzAG8AdQByAGMAZQBXAGkAZABnAGUAdAEAAAOXAAAA2gAAAAAAAAAA+wAAACoAUABMAFUARwBJAE4AMgAxADYAMwAxADMAMAA1ADEAMgAyADkANAA0ADEBAAACOQAAAHYAAAAAAAAAAPsAAAA+AEMAbABhAHMAcwBpAGYAaQBlAHIAUABsAHUAZwBpAG4AOgA6AFAAYQBsAGUAdAB0AGUAVwBpAGQAZwBlAHQBAAACVAAAAIIAAAAAAAAAAPsAAABEAEMAbABhAHMAcwBpAGYAaQBlAHIAUABsAHUAZwBpAG4AOgA6AEEAdAB0AHIAaQBiAHUAdABlAHMAVwBpAGQAZwBlAHQBAAAC2gAAAMQAAAAAAAAAAPsAAABAAEMAbABhAHMAcwBpAGYAaQBlAHIAUABsAHUAZwBpAG4AOgA6AFIAZQBzAG8AdQByAGMAZQBXAGkAZABnAGUAdAEAAAOiAAAAzwAAAAAAAAAA+wAAADgAQwBsAGEAcwBzAGkAZgBpAGUAcgBQAGwAdQBnAGkAbgA6ADoAQQB0AHQAcgBpAGIAdQB0AGUAcwEAAAGYAAABSQAAAAAAAAAA+wAAADYAQwBsAGEAcwBzAGkAZgBpAGUAcgBQAGwAdQBnAGkAbgA6ADoAUgBlAHMAbwB1AHIAYwBlAHMBAAAC5QAAAPYAAAAAAAAAAPsAAAAoAFAATABVAEcASQBOADEANwA2ADAAMQA5ADYAOQAzADEANwA3ADEAOQIAAAScAAAAPQAAAfQAAAJCAAAHgAAAAAAAAAAEAAAABAAAAAgAAAAI/AAAAAA=" + }, + "plugins": [ + { + "objectName": "PLUGIN155313125716068", + "typeUuid": "c38f75d4-7eca-11e4-b4a9-0800200c9a66", + "attributes": [ + { + "internalName": "settingsWindowVisibility", + "value": false + } + ] + }, + { + "objectName": "0cb8c032-2e46-46cb-89b4-731c631a069d", + "typeUuid": "c38f75e1-7eca-11e4-b4a9-0800200c9a66", + "attributes": [ + { + "internalName": "settingsWindowVisibility", + "value": false + } + ] + }, + { + "objectName": "8aebe35b-66e1-4cd0-835a-fd3637e4325a", + "typeUuid": "a9f94048-a277-11e5-bf7f-feff819cdc9f", + "attributes": [ + { + "internalName": "settingsWindowVisibility", + "value": false + } + ] + }, + { + "objectName": "d61e5b0a-ff05-4543-a5b1-501d6a0210f5", + "typeUuid": "1aea9056-26e3-11e5-b345-feff819cdc9f", + "attributes": [ + { + "internalName": "settingsWindowVisibility", + "value": false + } + ] + }, + { + "objectName": "8f59a3fe-e66d-4b86-9f5e-2dfb6595bec0", + "typeUuid": "30ca8a46-321b-11e5-a151-feff819cdc9f", + "attributes": [ + { + "internalName": "settingsWindowVisibility", + "value": false + }, + { + "internalName": "timeRange", + "value": 10.0 + }, + { + "internalName": "style", + "value": 4 + }, + { + "internalName": "showLatencyMarker", + "value": false + } + ] + }, + { + "objectName": "4cc794fe-4c11-4eb1-b9df-abb3775f9407", + "typeUuid": "c38fc3f4-7eca-11e4-b4a9-0800200c9a66", + "attributes": [ + { + "internalName": "settingsWindowVisibility", + "value": false + }, + { + "internalName": "multiView", + "value": true + }, + { + "internalName": "show0HzBin", + "value": false + }, + { + "internalName": "averageInterval", + "value": 1.0 + }, + { + "internalName": "horizontalView", + "value": true + } + ] + } + ] } \ No newline at end of file diff --git a/src/Studio/AppManager.cpp b/src/Studio/AppManager.cpp index 8ba55363..f47ee90d 100644 --- a/src/Studio/AppManager.cpp +++ b/src/Studio/AppManager.cpp @@ -24,6 +24,8 @@ // include precompiled header #include #include +#include +#include // include the required headers #include "AppManager.h" @@ -80,7 +82,8 @@ AppManager::AppManager(int argc, char* argv[]) // create the log file callback String logFilename = GetLogFilename(); - CORE_LOGMANAGER.CreateLogFile( logFilename.AsChar() ); + if (QDir().mkpath(QFileInfo(logFilename.AsChar()).absolutePath())) + CORE_LOGMANAGER.CreateLogFile( logFilename.AsChar() ); // log header LogInfo(); diff --git a/src/Studio/AppManager.h b/src/Studio/AppManager.h index c50c864d..a7a0a1e7 100644 --- a/src/Studio/AppManager.h +++ b/src/Studio/AppManager.h @@ -42,6 +42,7 @@ #include #include #include +#include // forward declarations QT_FORWARD_DECLARE_CLASS(QSplashScreen) @@ -108,7 +109,8 @@ class AppManager : public QObject void LogInfo(); - Core::String GetLogFilename() const { return QtBaseManager::GetAppDataFolder() + GetAppShortName() + "_Log.txt"; } + Core::String GetLogFilename() const { return QtBaseManager::GetAppDataFolder() + "Logs/" + GetAppShortName() + + FromQtString(QDateTime::currentDateTimeUtc().toString("_yyyy-MM-dd_hh-mm-ss")) + ".txt"; } // splash screen void SetSplashScreenMessage(const char* text) { mSplashScreen->showMessage(text, Qt::AlignBottom | Qt::AlignHCenter, Qt::white); } diff --git a/src/Studio/Plugins/Spectrogram2D/Spectrogram2DPlugin.cpp b/src/Studio/Plugins/Spectrogram2D/Spectrogram2DPlugin.cpp index 550dd457..1bfd54ac 100644 --- a/src/Studio/Plugins/Spectrogram2D/Spectrogram2DPlugin.cpp +++ b/src/Studio/Plugins/Spectrogram2D/Spectrogram2DPlugin.cpp @@ -60,9 +60,10 @@ void Spectrogram2DPlugin::RegisterAttributes() // register base class attributes Plugin::RegisterAttributes(); + bool multiViewChecked = true; // multi view AttributeSettings* attributeSettings = RegisterAttribute("Multi View", "multiView", "", ATTRIBUTE_INTERFACETYPE_CHECKBOX); - attributeSettings->SetDefaultValue(AttributeBool::Create(true)); + attributeSettings->SetDefaultValue(AttributeBool::Create(multiViewChecked)); // show zero Hz bin attributeSettings = RegisterAttribute("Show 0Hz Bin", "show0HzBin", "", Core::ATTRIBUTE_INTERFACETYPE_CHECKBOX); @@ -74,6 +75,10 @@ void Spectrogram2DPlugin::RegisterAttributes() attributeSettings->SetMinValue( AttributeFloat::Create(0.0f) ); attributeSettings->SetMaxValue( AttributeFloat::Create(10.0f) ); + // show zero Hz bin + attributeSettings = RegisterAttribute("Horizontal View", "horizontalView", "", Core::ATTRIBUTE_INTERFACETYPE_CHECKBOX); + attributeSettings->SetDefaultValue(AttributeBool::Create(true)); + // create default attribute values CreateDefaultAttributeValues(); } @@ -88,9 +93,24 @@ void Spectrogram2DPlugin::OnAttributeChanged(Property* property) if (propertyInternalName.IsEqual("averageInterval") == true) SetAverageInterval( property->AsFloat() ); + if (propertyInternalName.IsEqual("horizontalView") == true) { + bool checked = GetBoolAttributeByName("horizontalView"); + mSpectrumWidget->SetHorizontalView(GetBoolAttributeByName("horizontalView")); + } + // multi view - if (propertyInternalName.IsEqual("multiView") == true) - SetMultiView( GetMultiView() ); + if (propertyInternalName.IsEqual("multiView") == true) { + auto multiView = GetMultiView(); + enableHorizontalViewCheckbox(multiView); + SetMultiView( multiView ); + } +} + +void Spectrogram2DPlugin::enableHorizontalViewCheckbox(bool enable) +{ + if (mHorizontalViewCheckBox) { + mHorizontalViewCheckBox->setEnabled(enable); + } } @@ -124,10 +144,28 @@ bool Spectrogram2DPlugin::Init() // create the attribute set grid widget AttributeSetGridWidget* attributeSetGridWidget = new AttributeSetGridWidget( GetDockWidget() ); attributeSetGridWidget->ReInit(this); - connect( attributeSetGridWidget->GetPropertyManager(), SIGNAL(ValueChanged(Property*)), this, SLOT(OnAttributeChanged(Property*)) ); + + PropertyManager* propertyManager = attributeSetGridWidget->GetPropertyManager(); + + connect( propertyManager, SIGNAL(ValueChanged(Property*)), this, SLOT(OnAttributeChanged(Property*)) ); SetSettingsWidget( attributeSetGridWidget ); + auto properties = propertyManager->GetProperties(); + + for (uint32 i = 0; i < properties.Size(); ++i) { + auto& property = properties[i]; + const auto& name = property->GetAttributeSettings()->GetInternalNameString(); + if (name.IsEqual("horizontalView") == true) { + + mHorizontalViewCheckBox = property->GetAttributeWidget(); + mHorizontalViewCheckBox->setEnabled(GetMultiView()); + break; + } + } + + + /////////////////////////////////////////////////////////////////////////// // Add render widget at the end /////////////////////////////////////////////////////////////////////////// @@ -149,6 +187,8 @@ bool Spectrogram2DPlugin::Init() void Spectrogram2DPlugin::RealtimeUpdate() { + + udpateSelectedChannels(); // must always update spectrum analyzers const uint32 numAnalyzers = mSpectrumAnalyzers.Size(); for (uint32 i = 0; i < numAnalyzers; ++i) @@ -225,6 +265,9 @@ void Spectrogram2DPlugin::ReInit() SetMultiView( GetMultiView() ); + SetHorizontalView(GetHorizontalView()); + + // hide the used checkbox mChannelSelectionWidget->SetShowUsedVisible(false); } @@ -285,3 +328,20 @@ void Spectrogram2DPlugin::SetAverageInterval(double length) output->Reset(); } } + +void Spectrogram2DPlugin::udpateSelectedChannels() +{ + if (auto classifier = GetEngine()->GetActiveClassifier(); classifier) + { + if (auto selector = classifier->FindMainChannelSelector(); selector) + { + auto channels = mChannelSelectionWidget->GetAvailableChannels(); + for (uint32 i = 0; i < channels.Size(); ++i) + { + auto channel = channels[i]; + mChannelSelectionWidget->SetChecked(channel, selector->IsChannelSelected(channel)); + } + } + } + +} \ No newline at end of file diff --git a/src/Studio/Plugins/Spectrogram2D/Spectrogram2DPlugin.h b/src/Studio/Plugins/Spectrogram2D/Spectrogram2DPlugin.h index bc6df1bf..56e92855 100644 --- a/src/Studio/Plugins/Spectrogram2D/Spectrogram2DPlugin.h +++ b/src/Studio/Plugins/Spectrogram2D/Spectrogram2DPlugin.h @@ -65,10 +65,16 @@ class Spectrogram2DPlugin : public Plugin void SetMultiView(bool multiView) { if (mSpectrumWidget != NULL) mSpectrumWidget->SetMultiView(multiView); } inline double GetAverageInterval() { return GetFloatAttributeByName("averageInterval"); } void SetAverageInterval(double length); + void SetHorizontalView(bool horizontalView) { if (mSpectrumWidget != NULL) mSpectrumWidget->SetHorizontalView(horizontalView); } + inline bool GetHorizontalView() const { return GetBoolAttributeByName("horizontalView"); } private slots: void OnChannelSelectionChanged(); void OnAttributeChanged(Property* property); + + private: + void enableHorizontalViewCheckbox(bool enable); + void udpateSelectedChannels(); private: // selected channels @@ -77,6 +83,7 @@ class Spectrogram2DPlugin : public Plugin Core::Array mAverageSpectra; // holds the current average (the display values) ChannelMultiSelectionWidget* mChannelSelectionWidget; + QWidget* mHorizontalViewCheckBox = nullptr; Spectrogram2DWidget* mSpectrumWidget; Core::Array mSpectrumElements; diff --git a/src/Studio/Plugins/Spectrogram2D/Spectrogram2DWidget.cpp b/src/Studio/Plugins/Spectrogram2D/Spectrogram2DWidget.cpp index 49751542..237590f4 100644 --- a/src/Studio/Plugins/Spectrogram2D/Spectrogram2DWidget.cpp +++ b/src/Studio/Plugins/Spectrogram2D/Spectrogram2DWidget.cpp @@ -55,7 +55,6 @@ Spectrogram2DWidget::Spectrogram2DWidget(Spectrogram2DPlugin* plugin, QWidget* p setMinimumHeight(150); } - // destructor Spectrogram2DWidget::~Spectrogram2DWidget() { @@ -66,9 +65,7 @@ Spectrogram2DWidget::~Spectrogram2DWidget() // render frame void Spectrogram2DWidget::paintGL() { - uint32 numSpectrums = 0; - - numSpectrums = mSpectrums.Size(); + uint32 numSpectrums = mSpectrums.Size(); mLeftTextWidth = 50; // TODO HACK /* Classifier* classifier = GetClassifier(); if (classifier != NULL) @@ -114,8 +111,19 @@ void Spectrogram2DWidget::paintGL() } else { - uint32 numAreasInColumn = Math::Sqrt(numSpectrums) + 0.5; - RenderSplitViews( numSpectrums, numAreasInColumn ); + uint32 numAreasOnLine; + if (!mHorizontalView){ + numAreasOnLine = Math::Sqrt(numSpectrums) + 0.5; + } + else + { + numAreasOnLine = 2; + if (numSpectrums % 2 == 1) + { + --numSpectrums; + } + } + RenderSplitViews( numSpectrums, numAreasOnLine, 2.0, mHorizontalView ); } // post rendering @@ -126,6 +134,7 @@ void Spectrogram2DWidget::paintGL() // render callback void Spectrogram2DWidget::RenderCallback::Render(uint32 index, bool isHighlighted, double x, double y, double width, double height) { + // base class render OpenGLWidgetCallback::Render( index, isHighlighted, x, y, width, height ); @@ -227,26 +236,37 @@ void Spectrogram2DWidget::RenderCallback::Render(uint32 index, bool isHighlighte double rangeMax = mCurMaxRange; + + const double minFrequency = spectrumElement->mSpectrum->CalcFrequency(minBinIndex); + const double maxFrequency = spectrumElement->mSpectrum->CalcFrequency(numBins-1); + + mParentWidget->mGridInfo.mNumXSplits = Math::NextPowerOfTwo( mParent->devicePixelRatio() * (width / 100) ); mParentWidget->mGridInfo.mNumXSubSplits = 0; - mParentWidget->mGridInfo.mNumYSplits = Math::NextPowerOfTwo( mParent->devicePixelRatio() * (height / 75) ); - mParentWidget->mGridInfo.mNumYSubSplits = 4; + mParentWidget->mGridInfo.mNumYSplits = mParentWidget->GetMultiView() && mParentWidget->GetHorizontalView() // for horizontal view we want to densly place splits + ? ((maxFrequency - minFrequency) / (mParent->devicePixelRatio() * 5)) : + Math::NextPowerOfTwo( mParent->devicePixelRatio() * (height / 75) ); + mParentWidget->mGridInfo.mNumYSubSplits = 5; OpenGLWidget2DHelpers::RenderGrid( this, &mParentWidget->mGridInfo, FromQtColor(gridColor), FromQtColor(subGridColor), areaStartX, areaEndX, areaStartY, areaEndY ); if (numBins == 0) return; + // in the case of horizontal views, we show 2 charts per row, thus mParentWidget->mSpectrums.Size() / 2 - 1 is the first index of right column charts + bool drawFromRight = (index % 2 == 0); // render spectrum if (mParentWidget->GetMultiView() == true) - OpenGLWidget2DHelpers::RenderSpectrumChart( this, spectrumElement->mSpectrum, spectrumElement->mColor, minBinIndex, numBins -1, rangeMin, rangeMax, areaStartX, areaEndX, areaStartY, areaEndY); + { + OpenGLWidget2DHelpers::RenderSpectrumChart( this, spectrumElement->mSpectrum, spectrumElement->mColor, minFrequency, maxFrequency, rangeMin, rangeMax, areaStartX, areaEndX, areaStartY, areaEndY, mParentWidget->GetHorizontalView(), drawFromRight); + } else { // iterate through all spectra and render each for (uint32 i=0; imSpectrums[i]; - OpenGLWidget2DHelpers::RenderSpectrum(this, currentElement.mSpectrum, currentElement.mColor, minBinIndex, numBins - 1, rangeMin, rangeMax, areaStartX, areaEndX, areaStartY, areaEndY); + OpenGLWidget2DHelpers::RenderSpectrum(this, currentElement.mSpectrum, currentElement.mColor, minFrequency, maxFrequency, rangeMin, rangeMax, areaStartX, areaEndX, areaStartY, areaEndY); } } @@ -255,18 +275,23 @@ void Spectrogram2DWidget::RenderCallback::Render(uint32 index, bool isHighlighte //const int textHeight = GetTextHeight(); const int textRectWidth = areaStartX - 5; - // render Y axis labels - OpenGLWidget2DHelpers::RenderEquallySpacedYLabels( this, mTempString, textColor, rangeMin, rangeMax, mParentWidget->mGridInfo.mNumYSplits, 0.0, 0.0, textRectWidth, areaHeight ); - - // render X axis labels - const double minFrequency = spectrumElement->mSpectrum->CalcFrequency(minBinIndex); - const double maxFrequency = spectrumElement->mSpectrum->CalcFrequency(numBins-1); - OpenGLWidget2DHelpers::RenderEquallySpacedXLabels( this, mTempString, "Hz", textColor, minFrequency, maxFrequency, mParentWidget->mGridInfo.mNumXSplits, areaStartX, height-areaHeight, width-areaStartX, areaHeight ); + auto horizontalView = mParentWidget->GetHorizontalView(); + if (mParentWidget->GetMultiView() == false || mParentWidget->GetHorizontalView() == false) { + // render Y axis labels + OpenGLWidget2DHelpers::RenderEquallySpacedYLabels( this, mTempString, textColor, rangeMin, rangeMax, mParentWidget->mGridInfo.mNumYSplits, 0.0, 0.0, textRectWidth, areaHeight ); + // render X axis labels + OpenGLWidget2DHelpers::RenderEquallySpacedXLabels( this, mTempString, "Hz", textColor, minFrequency, maxFrequency, mParentWidget->mGridInfo.mNumXSplits, areaStartX, height-areaHeight, width-areaStartX, areaHeight ); + } else { + // render Y axis labels + OpenGLWidget2DHelpers::RenderEquallySpacedYLabels( this, mTempString, textColor, minFrequency, maxFrequency, mParentWidget->mGridInfo.mNumYSplits, 0.0, 0.0, textRectWidth, areaHeight, horizontalView, drawFromRight, mParentWidget->mGridInfo.mNumYSubSplits); + // render X axis labels + OpenGLWidget2DHelpers::RenderEquallySpacedXLabels( this, mTempString, "Hz", textColor, rangeMin, rangeMax, mParentWidget->mGridInfo.mNumXSplits, areaStartX, height-areaHeight, width-areaStartX, areaHeight, horizontalView, drawFromRight ); + } // render feedback name - if (spectrumElement != NULL && mParentWidget->mMultiView == true) + if (spectrumElement != NULL && mParentWidget->GetMultiView() == true) { mTempString.Format("%s", spectrumElement->mName.AsChar()); RenderText( mTempString.AsChar(), GetOpenGLWidget()->GetDefaultFontSize(), spectrumElement->mColor, areaStartX+textMargin, 0, OpenGLWidget::ALIGN_TOP| OpenGLWidget::ALIGN_LEFT ); } -} +} \ No newline at end of file diff --git a/src/Studio/Plugins/Spectrogram2D/Spectrogram2DWidget.h b/src/Studio/Plugins/Spectrogram2D/Spectrogram2DWidget.h index 2e23dfb1..ce626ee7 100644 --- a/src/Studio/Plugins/Spectrogram2D/Spectrogram2DWidget.h +++ b/src/Studio/Plugins/Spectrogram2D/Spectrogram2DWidget.h @@ -55,9 +55,12 @@ class Spectrogram2DWidget : public OpenGLWidget void UpdateSpectrum(uint32 index, Spectrum* spectrum) { mSpectrums[index].mSpectrum = spectrum; } void ReInit(const Core::Array& spectrums) { mSpectrums = spectrums; } + uint32 GetNumberOfSpectrums() { return mSpectrums.Size(); } bool GetMultiView() const { return mMultiView; } void SetMultiView(bool multiView) { mMultiView = multiView; } + void SetHorizontalView(bool horizontalView) { mHorizontalView = horizontalView; } + bool GetHorizontalView() const { return mHorizontalView; } private: class RenderCallback : public OpenGLWidgetCallback @@ -81,6 +84,7 @@ class Spectrogram2DWidget : public OpenGLWidget Core::Array mSpectrums; double mLeftTextWidth; bool mMultiView; + bool mHorizontalView = true; QColor mGridColor; QColor mSubGridColor; diff --git a/src/Studio/Plugins/View/ViewPlugin.cpp b/src/Studio/Plugins/View/ViewPlugin.cpp index 7914274e..25635ee2 100644 --- a/src/Studio/Plugins/View/ViewPlugin.cpp +++ b/src/Studio/Plugins/View/ViewPlugin.cpp @@ -196,3 +196,18 @@ void ViewPlugin::SetViewDuration(double seconds) classifier->GetViewNode(i)->SetViewDuration(seconds); } +double ViewPlugin::GetFixedLength() +{ + Classifier* classifier = GetEngine()->GetActiveClassifier(); + CORE_ASSERT(classifier); + + const uint32 numViewNodes = classifier->GetNumViewNodes(); + for (uint32 i=0; iGetViewNode(i)->GetFixedLength(); + if (fixedLength > 0.) + return fixedLength; + } + return -1.; +} + diff --git a/src/Studio/Plugins/View/ViewPlugin.h b/src/Studio/Plugins/View/ViewPlugin.h index 711063f4..34062751 100644 --- a/src/Studio/Plugins/View/ViewPlugin.h +++ b/src/Studio/Plugins/View/ViewPlugin.h @@ -77,6 +77,7 @@ class ViewPlugin : public Plugin, public Core::EventHandler Core::Color GetChannelColor(uint32 multichannel, uint32 index); void SetViewDuration(double seconds); + double GetFixedLength(); private slots: void OnAttributeChanged(Property* property); diff --git a/src/Studio/Plugins/View/ViewWidget.cpp b/src/Studio/Plugins/View/ViewWidget.cpp index c03d6f74..e7207889 100644 --- a/src/Studio/Plugins/View/ViewWidget.cpp +++ b/src/Studio/Plugins/View/ViewWidget.cpp @@ -116,6 +116,13 @@ void ViewWidget::RenderCallback::Render(uint32 index, bool isHighlighted, double { //Classifier* classifier = mViewWidget->GetClassifier(); ViewPlugin* plugin = mViewWidget->GetPlugin(); + + double maxTime = plugin->GetFixedLength() * 60; // fixed length is kept in mins + double timeRange = plugin->GetTimeRange(); + if (maxTime < 0.) + maxTime = GetEngine()->GetElapsedTime().InSeconds(); + else + timeRange = maxTime; // get channel and its properties const MultiChannel& channels = plugin->GetMultiChannel(index); @@ -161,7 +168,6 @@ void ViewWidget::RenderCallback::Render(uint32 index, bool isHighlighted, double // automatically calculated, do not change these const double areaStartX = mViewWidget->mLeftTextWidth; const double areaWidth = width - areaStartX; - const double maxTime = GetEngine()->GetElapsedTime().InSeconds(); // assumes all channels have same elapsed time // draw background rect AddRect( 0, 0, width, height, FromQtColor(backgroundColor) ); @@ -171,7 +177,6 @@ void ViewWidget::RenderCallback::Render(uint32 index, bool isHighlighted, double RenderRects(); // calculate the time scale - double timeRange = plugin->GetTimeRange(); bool drawLatencyMarker = plugin->GetShowLatencyMarker(); @@ -259,8 +264,18 @@ void ViewWidget::RenderCallback::RenderTimeline(double x, double y, double width const double areaWidth = width - areaStartX; QColor color = ColorPalette::Shared::GetTextQColor(); - const double timeRange = mViewWidget->GetPlugin()->GetTimeRange(); - const double maxTime = GetEngine()->GetElapsedTime().InSeconds(); + double timeRange = mViewWidget->GetPlugin()->GetTimeRange(); + ViewPlugin* plugin = mViewWidget->GetPlugin(); + + double maxTime = plugin->GetFixedLength(); + bool scaleInMins = false; + if (maxTime < 0.) + maxTime = GetEngine()->GetElapsedTime().InSeconds(); + else + { + scaleInMins = true; + timeRange = maxTime; + } - OpenGLWidget2DHelpers::RenderTimeline( this, FromQtColor(color), timeRange, maxTime, areaStartX, y, areaWidth, height, mTempString ); + OpenGLWidget2DHelpers::RenderTimeline( this, FromQtColor(color), timeRange, maxTime, areaStartX, y, areaWidth, height, mTempString, scaleInMins); } diff --git a/src/Studio/Rendering/OpenGLWidget.cpp b/src/Studio/Rendering/OpenGLWidget.cpp index 7b0b17ff..607b4cbb 100644 --- a/src/Studio/Rendering/OpenGLWidget.cpp +++ b/src/Studio/Rendering/OpenGLWidget.cpp @@ -681,7 +681,7 @@ void OpenGLWidget::PostRendering() // render each sensor in an individual view -void OpenGLWidget::RenderSplitViews(uint32 numAreas, uint32 numAreasInColumn, double border) +void OpenGLWidget::RenderSplitViews(uint32 numAreas, uint32 numAreasOnLine, double border, bool horizontalView) { // get the current local cursor pos QPoint localCursorPos = mapFromGlobal( QCursor::pos() ); @@ -692,41 +692,73 @@ void OpenGLWidget::RenderSplitViews(uint32 numAreas, uint32 numAreasInColumn, do // iterate through the areas uint32 numActuallyRendered = 0; + + // the number of sensors to be shown within one column (vertically) before we start a new column and split the screen to fit more channels + bool useSeveralColumns = true; + if (numAreasOnLine == CORE_INVALIDINDEX32) + useSeveralColumns = false; + for (uint32 i=0; iIsChannelSelected(sensor->GetChannel()) == false) // continue; - // the number of sensors to be shown within one column (vertically) before we start a new column and split the screen to fit more channels - bool useSeveralColumns = true; - if (numAreasInColumn == CORE_INVALIDINDEX32) - useSeveralColumns = false; - double xStart, yStart, xEnd, yEnd, cellWidth, cellHeight; - if (useSeveralColumns == true && numAreas > numAreasInColumn) + if (horizontalView) { - // split the viewport vertically and if there are too many items, also split up into columns - const uint32 numColumns = ceil( (double)numAreas / (double)numAreasInColumn ); - const double columnWidth= (double)mWidth / (double)numColumns; - const uint32 columnNr = numActuallyRendered / numAreasInColumn; - - xStart = columnNr * columnWidth; - xEnd = xStart + columnWidth; - cellHeight = (double)viewportHeight / (double)numAreasInColumn; - cellWidth = columnWidth; - yStart = (numActuallyRendered % numAreasInColumn) * cellHeight; - yEnd = yStart + cellHeight; + if (numAreas > numAreasOnLine) + { + // Determine the number of rows required. + const uint32 numRows = uint32(ceil((double)numAreas / (double)numAreasOnLine)); + cellWidth = (double)mWidth / (double)numAreasOnLine; + cellHeight = viewportHeight / (double)numRows; + // Calculate the row and column for the current area. + const uint32 rowNr = numActuallyRendered / numAreasOnLine; + const uint32 colNr = numActuallyRendered % numAreasOnLine; + + xStart = colNr * cellWidth; + xEnd = xStart + cellWidth; + yStart = rowNr * cellHeight; + yEnd = yStart + cellHeight; + } + else + { + // Single row: distribute all areas evenly along the width. + cellWidth = (double)mWidth / (double)numAreas; + cellHeight = viewportHeight; + xStart = numActuallyRendered * cellWidth; + xEnd = xStart + cellWidth; + yStart = 0.0; + yEnd = viewportHeight; + } } else { - // split the viewport vertically (only!) and resize the the items based on the number of sensors to be rendered - xStart = 0.0; - xEnd = mWidth; - cellHeight = (double)viewportHeight / (double)numAreas; - cellWidth = mWidth; - yStart = numActuallyRendered * cellHeight; - yEnd = yStart + cellHeight; + if (useSeveralColumns && numAreas > numAreasOnLine) + { + // Calculate how many columns are needed. + const uint32 numColumns = uint32(ceil((double)numAreas / (double)numAreasOnLine)); + const double columnWidth = (double)mWidth / (double)numColumns; + const uint32 columnNr = numActuallyRendered / numAreasOnLine; + + xStart = columnNr * columnWidth; + xEnd = xStart + columnWidth; + cellHeight = viewportHeight / (double)numAreasOnLine; + cellWidth = columnWidth; + yStart = (numActuallyRendered % numAreasOnLine) * cellHeight; + yEnd = yStart + cellHeight; + } + else + { + // Single column: split the viewport vertically. + xStart = 0.0; + xEnd = mWidth; + cellHeight = viewportHeight / (double)numAreas; + cellWidth = mWidth; + yStart = numActuallyRendered * cellHeight; + yEnd = yStart + cellHeight; + } } xStart = (int32)xStart + 0.5; diff --git a/src/Studio/Rendering/OpenGLWidget.h b/src/Studio/Rendering/OpenGLWidget.h index 69fcddab..2776b69c 100644 --- a/src/Studio/Rendering/OpenGLWidget.h +++ b/src/Studio/Rendering/OpenGLWidget.h @@ -53,7 +53,8 @@ class OpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions bool PreRendering(); void PostRendering(); void Render(double border=2.0) { RenderSplitViews(1, CORE_INVALIDINDEX32, border); } - void RenderSplitViews(uint32 numAreas, uint32 numAreasInColumn=CORE_INVALIDINDEX32, double border=2.0); + // by default numAerasOnLIne is the number of areas vertically + void RenderSplitViews(uint32 numAreas, uint32 numAreasOnLine=CORE_INVALIDINDEX32, double border=2.0, bool horizontalView = false); int GetHeight() const { return mHeight; } diff --git a/src/Studio/Rendering/OpenGLWidget2DHelpers.cpp b/src/Studio/Rendering/OpenGLWidget2DHelpers.cpp index dbf1b23e..b0a38ff5 100644 --- a/src/Studio/Rendering/OpenGLWidget2DHelpers.cpp +++ b/src/Studio/Rendering/OpenGLWidget2DHelpers.cpp @@ -28,6 +28,8 @@ #include "OpenGLWidget2DHelpers.h" #include +#include + using namespace Core; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -68,16 +70,16 @@ void OpenGLWidget2DHelpers::RenderHGrid(OpenGLWidgetCallback* callback, uint32 n void OpenGLWidget2DHelpers::RenderGrid(OpenGLWidgetCallback* callback, GridInfo* gridInfo, const Core::Color& mainColor, const Core::Color& subColor, double xStart, double xEnd, double yStart, double yEnd) { const double width = xEnd - xStart; - const double height = yEnd - yStart; - const double splitDeltaY = height / (double)gridInfo->mNumYSplits; - const double subSplitDeltaY = splitDeltaY / (double)gridInfo->mNumYSubSplits; - const double splitDeltaX = width / (double)gridInfo->mNumXSplits; - const double subSplitDeltaX = splitDeltaX / (double)gridInfo->mNumXSubSplits; + const double height = yEnd - yStart; + const double splitDeltaY = height / (double)gridInfo->mNumYSplits; + const double subSplitDeltaY = splitDeltaY / (double)gridInfo->mNumYSubSplits; + const double splitDeltaX = width / (double)gridInfo->mNumXSplits; + const double subSplitDeltaX = splitDeltaX / (double)gridInfo->mNumXSubSplits; // render main horizontal lines for (uint32 i=0; i<=gridInfo->mNumYSplits; ++i) { - double splitY = yStart + height - i*splitDeltaY; + double splitY = yStart + height - i*splitDeltaY; callback->AddLine( xStart, splitY, mainColor, xEnd, splitY, mainColor ); } @@ -86,11 +88,11 @@ void OpenGLWidget2DHelpers::RenderGrid(OpenGLWidgetCallback* callback, GridInfo* { for (uint32 i=0; imNumYSplits; ++i) { - double splitY = yStart + height - i*splitDeltaY; + double splitY = yStart + height - i*splitDeltaY; for (uint32 j=1; jmNumYSubSplits; ++j) { - double subSplitY = splitY - j*subSplitDeltaY; + double subSplitY = splitY - j*subSplitDeltaY; callback->AddLine( xStart, subSplitY, subColor, xEnd, subSplitY, subColor ); } } @@ -100,7 +102,8 @@ void OpenGLWidget2DHelpers::RenderGrid(OpenGLWidgetCallback* callback, GridInfo* // render main vertical lines for (uint32 i=0; i<=gridInfo->mNumXSplits; ++i) { - double splitX = xStart + width - i*splitDeltaX; + double splitX = xStart + width - i*splitDeltaX; + callback->AddLine( splitX, yStart, mainColor, splitX, yEnd, mainColor ); } @@ -109,11 +112,11 @@ void OpenGLWidget2DHelpers::RenderGrid(OpenGLWidgetCallback* callback, GridInfo* { for (uint32 i=0; imNumXSplits; ++i) { - double splitX = xStart + width - i*splitDeltaX; + double splitX = xStart + width - i*splitDeltaX; for (uint32 j=1; jmNumXSubSplits; ++j) { - double subSplitX = splitX - j*subSplitDeltaX; + double subSplitX = splitX - j*subSplitDeltaX; callback->AddLine( subSplitX, yStart, subColor, subSplitX, yEnd, subColor ); } } @@ -123,108 +126,176 @@ void OpenGLWidget2DHelpers::RenderGrid(OpenGLWidgetCallback* callback, GridInfo* } -void OpenGLWidget2DHelpers::RenderEquallySpacedYLabels(OpenGLWidgetCallback* callback, String& tempString, const QColor& textColor, double minValue, double maxValue, uint32 numSplits, double x, double y, double width, double height) +void OpenGLWidget2DHelpers::RenderEquallySpacedYLabels(OpenGLWidgetCallback* callback, String& tempString, const QColor& textColor, double minValue, double maxValue, uint32 numSplits, double x, double y, double width, double height, bool horizontalView, bool drawFromRight, uint32 numSubsplits) { - const double range = Max(minValue, maxValue) - Min(minValue, maxValue); + const double range = Max(minValue, maxValue) - Min(minValue, maxValue); - double splitRange = 0.0; - if (numSplits > 0) - splitRange = range / numSplits; + double splitRange = 0.0; + if (numSplits > 0) + splitRange = range / numSplits; - //QRect textRect; - //const double textHeight = callback->GetTextHeight(); - //const int textMarginX = 2; + //QRect textRect; + //const double textHeight = callback->GetTextHeight(); + //const int textMarginX = 2; - // render min value at the bottom - tempString.Format("%.2f", minValue); - //textRect = QRect(x, y+height-textHeight, width, textHeight); + const char* unit = "Hz"; - callback->RenderText( tempString.AsChar(), callback->GetOpenGLWidget()->GetDefaultFontSize(), textColor, width, height, OpenGLWidget::ALIGN_BASELINE | OpenGLWidget::ALIGN_RIGHT ); + // render min value at the bottom + float fontSize = callback->GetOpenGLWidget()->GetDefaultFontSize(); + if (horizontalView == false) { + tempString.Format("%.2f", minValue); + } else { + fontSize -= 3; + tempString.Format("%.2f %s", minValue, unit); + } + //textRect = QRect(x, y+height-textHeight, width, textHeight); - // render values for the in between splits - for (uint32 i=1; iRenderText( tempString.AsChar(), fontSize, textColor, width, height, OpenGLWidget::ALIGN_BASELINE | OpenGLWidget::ALIGN_RIGHT ); + + String subline = "-"; + if (horizontalView && numSubsplits > 1) { - double y = (numSplits-i)*(height/numSplits); + for (uint32 j = 1; j < numSubsplits; ++j) + { + double y1 = height - ((height / numSplits) / numSubsplits) * j; + callback->RenderText(subline.AsChar(), fontSize + 2, textColor, width, y1, OpenGLWidget::ALIGN_MIDDLE | OpenGLWidget::ALIGN_RIGHT); + } + } + + // render values for the in between splits + for (uint32 i=1; i 5.0) + tempString.Format("%.0f", value); + else if (splitRange > 1.0) + tempString.Format("%.1f", value); + else + tempString.Format("%.2f", value); + + callback->RenderText( tempString.AsChar(), fontSize, textColor, width, y, OpenGLWidget::ALIGN_MIDDLE | OpenGLWidget::ALIGN_RIGHT ); + } else { + if (splitRange > 5.0) + tempString.Format("%.0f %s", value, unit); + else if (splitRange > 1.0) + tempString.Format("%.1f %s", value, unit); + else + tempString.Format("%.2f %s", value, unit); + + callback->RenderText( tempString.AsChar(), fontSize, textColor, width, y, OpenGLWidget::ALIGN_MIDDLE | OpenGLWidget::ALIGN_RIGHT ); + + if (numSubsplits > 1) { + for (uint32 j = 1; j < numSubsplits; ++j) { + double y1 = y - ((height / numSplits) / numSubsplits) * j; + + // const double value = ClampedRemapRange( (double)i/numSplits, 0.0, 1.0, minValue, maxValue ); + callback->RenderText( subline.AsChar(), fontSize + 2, textColor, width, y1, OpenGLWidget::ALIGN_MIDDLE | OpenGLWidget::ALIGN_RIGHT ); + } + } + } - const double value = ClampedRemapRange( (double)i/numSplits, 0.0, 1.0, minValue, maxValue ); + //textRect = QRect(x, y+height-yOffset-(textHeight/2), width, textHeight); - // draw the timestamp label + + + // if () + } + + // render max value on top + if (horizontalView == false) { if (splitRange > 5.0) - tempString.Format("%.0f", value); + tempString.Format("%.0f", maxValue); else if (splitRange > 1.0) - tempString.Format("%.1f", value); + tempString.Format("%.1f", maxValue); else - tempString.Format("%.2f", value); - - //textRect = QRect(x, y+height-yOffset-(textHeight/2), width, textHeight); - - callback->RenderText( tempString.AsChar(), callback->GetOpenGLWidget()->GetDefaultFontSize(), textColor, width, y, OpenGLWidget::ALIGN_MIDDLE | OpenGLWidget::ALIGN_RIGHT ); + tempString.Format("%.2f", maxValue); + } else { + if (splitRange > 5.0) + tempString.Format("%.0f %s", maxValue, unit); + else if (splitRange > 1.0) + tempString.Format("%.1f %s", maxValue, unit); + else + tempString.Format("%.2f %s", maxValue, unit); } - // render max value on top - if (splitRange > 5.0) - tempString.Format("%.0f", maxValue); - else if (splitRange > 1.0) - tempString.Format("%.1f", maxValue); - else - tempString.Format("%.2f", maxValue); + //textRect = QRect(x, y, width, textHeight); - //textRect = QRect(x, y, width, textHeight); - - callback->RenderText( tempString.AsChar(), callback->GetOpenGLWidget()->GetDefaultFontSize(), textColor, width, 0, OpenGLWidget::ALIGN_TOP | OpenGLWidget::ALIGN_RIGHT ); + callback->RenderText( tempString.AsChar(), fontSize, textColor, width, 0, OpenGLWidget::ALIGN_TOP | OpenGLWidget::ALIGN_RIGHT ); } -void OpenGLWidget2DHelpers::RenderEquallySpacedXLabels(OpenGLWidgetCallback* callback, String& tempString, const char* unit, const QColor& textColor, double minValue, double maxValue, uint32 numSplits, double x, double y, double width, double height) +void OpenGLWidget2DHelpers::RenderEquallySpacedXLabels(OpenGLWidgetCallback* callback, String& tempString, const char* unit, const QColor& textColor, double minValue, double maxValue, uint32 numSplits, double x, double y, double width, double height, bool horizontalView, bool drawFromRight) { - const double range = Max(minValue, maxValue) - Min(minValue, maxValue); - - double splitRange = 0.0; - if (numSplits > 0) - splitRange = range / numSplits; + const double range = Max(minValue, maxValue) - Min(minValue, maxValue); - //QRect textRect; - for (uint32 i=0; i<=numSplits; ++i) - { - double xOffset = x + i*(width/numSplits); + double splitRange = 0.0; + if (numSplits > 0) + splitRange = range / numSplits; - const double value = ClampedRemapRange( (double)i/numSplits, 0.0, 1.0, minValue, maxValue ); + //QRect textRect; + for (uint32 i=0; i<=numSplits; ++i) + { + double xOffset = x + i*(width/numSplits); - // draw the timestamp label - if (splitRange > 1.0) - tempString.Format("%.0f %s", value, unit); - else if (splitRange > 0.1) - tempString.Format("%.1f %s", value, unit); - else - tempString.Format("%.2f %s", value, unit); + double value = ClampedRemapRange( (double)i/numSplits, 0.0, 1.0, minValue, maxValue ); - //double textWidth = callback->CalcTextWidth( tempString.AsChar() ); - //const int rectHeight = 9; - //const int rectY = y + 3; - - if (i == 0) + if (horizontalView && drawFromRight) { - //textRect = QRect(x, rectY, textWidth, rectHeight); - callback->RenderText( tempString.AsChar(), callback->GetOpenGLWidget()->GetDefaultFontSize(), textColor, x, y+height, OpenGLWidget::ALIGN_BASELINE | OpenGLWidget::ALIGN_LEFT ); + value = ClampedRemapRange( (double)(numSplits - i)/numSplits, 0.0, 1.0, minValue, maxValue ); } - else if (i == numSplits) - { - //textRect = QRect(x+width-textWidth, rectY, textWidth, rectHeight); - callback->RenderText( tempString.AsChar(), callback->GetOpenGLWidget()->GetDefaultFontSize(), textColor, x+width, y+height, OpenGLWidget::ALIGN_BASELINE | OpenGLWidget::ALIGN_RIGHT ); - } - else - { - //double textX = xOffset-(textWidth*0.5); - //textRect = QRect(textX, rectY, textWidth, rectHeight); - if (xOffset > 0) - { - callback->RenderText( tempString.AsChar(), callback->GetOpenGLWidget()->GetDefaultFontSize(), textColor, xOffset, y+height, OpenGLWidget::ALIGN_BASELINE | OpenGLWidget::ALIGN_CENTER ); - } + // draw the timestamp label + if (horizontalView == false) { + if (splitRange > 1.0) + tempString.Format("%.0f %s", value, unit); + else if (splitRange > 0.1) + tempString.Format("%.1f %s", value, unit); + else + tempString.Format("%.2f %s", value, unit); + } else { + if (splitRange > 1.0) + tempString.Format("%.0f", value); + else if (splitRange > 0.1) + tempString.Format("%.1f", value); + else + tempString.Format("%.2f", value); } - } + + + //double textWidth = callback->CalcTextWidth( tempString.AsChar() ); + //const int rectHeight = 9; + //const int rectY = y + 3; + + if (i == 0) + { + //textRect = QRect(x, rectY, textWidth, rectHeight); + callback->RenderText( tempString.AsChar(), callback->GetOpenGLWidget()->GetDefaultFontSize(), textColor, x, y+height, OpenGLWidget::ALIGN_BASELINE | OpenGLWidget::ALIGN_LEFT ); + } + else if (i == numSplits) + { + //textRect = QRect(x+width-textWidth, rectY, textWidth, rectHeight); + + callback->RenderText( tempString.AsChar(), callback->GetOpenGLWidget()->GetDefaultFontSize(), textColor, x+width, y+height, OpenGLWidget::ALIGN_BASELINE | OpenGLWidget::ALIGN_RIGHT ); + } + else + { + //double textX = xOffset-(textWidth*0.5); + //textRect = QRect(textX, rectY, textWidth, rectHeight); + if (xOffset > 0) + { + callback->RenderText( tempString.AsChar(), callback->GetOpenGLWidget()->GetDefaultFontSize(), textColor, xOffset, y+height, OpenGLWidget::ALIGN_BASELINE | OpenGLWidget::ALIGN_CENTER ); + } + } + } } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Signal Rendering ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -232,19 +303,19 @@ void OpenGLWidget2DHelpers::RenderEquallySpacedXLabels(OpenGLWidgetCallback* cal // calculate automatic splitting void OpenGLWidget2DHelpers::AutoCalcChartSplits(double height, uint32* outNumSplits, uint32* outNumSubSplits) { - *outNumSplits = 2; *outNumSubSplits = 0; - - if (height > 200) - { - *outNumSplits = 10; *outNumSubSplits = 5; - return; - } - - if (height > 75) - { - *outNumSplits = 4; *outNumSubSplits = 2; - return; - } + *outNumSplits = 2; *outNumSubSplits = 0; + + if (height > 200) + { + *outNumSplits = 10; *outNumSubSplits = 5; + return; + } + + if (height > 75) + { + *outNumSplits = 4; *outNumSubSplits = 2; + return; + } } @@ -805,7 +876,7 @@ void OpenGLWidget2DHelpers::RenderLineSampleClipped(OpenGLWidgetCallback* callba // Timeline ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -void OpenGLWidget2DHelpers::RenderTimeline(OpenGLWidgetCallback* callback, const Color& color, double timeRange, double maxTime, double x, double y, double width, double height, String& tempString) +void OpenGLWidget2DHelpers::RenderTimeline(OpenGLWidgetCallback* callback, const Color& color, double timeRange, double maxTime, double x, double y, double width, double height, String& tempString, bool scaleInMins) { // parameters uint32 xStart = x; @@ -814,7 +885,7 @@ void OpenGLWidget2DHelpers::RenderTimeline(OpenGLWidgetCallback* callback, const // calc remaining parameters const int32 numPixels = xEnd - xStart; // width of the chart area in pixels - double minTime = maxTime - timeRange; // time of first pixel + double minTime = maxTime - timeRange; // time of first pixel // div by zero check if (IsClose(timeRange, 0.0, Math::epsilon) == true) @@ -850,8 +921,17 @@ void OpenGLWidget2DHelpers::RenderTimeline(OpenGLWidgetCallback* callback, const // draw a time tick callback->AddLine( x+0.375, windowHeight-9, color, x+0.375, windowHeight-14, color ); + if (scaleInMins) + { // draw the timestamp label - tempString.Format( "%.1f", time); + double timeInSecs = time * 60.; + int minutes = static_cast(timeInSecs) / 60; + int seconds = static_cast(timeInSecs) % 60; + + tempString.Format("%02d:%02d", minutes, seconds); + } + else + tempString.Format( "%.1f", time); //double textWidth = callback->CalcTextWidth( tempString.AsChar() ); if (x > 0) @@ -903,7 +983,7 @@ void OpenGLWidget2DHelpers::RenderSpectrum(OpenGLWidgetCallback* callback, Spect // render spectrum -void OpenGLWidget2DHelpers::RenderSpectrumChart(OpenGLWidgetCallback* callback, Spectrum* spectrum, const Color& color, uint32 minBinIndex, uint32 maxBinIndex, double rangeMin, double rangeMax, double xStart, double xEnd, double yStart, double yEnd) +void OpenGLWidget2DHelpers::RenderSpectrumChart(OpenGLWidgetCallback* callback, Spectrum* spectrum, const Color& color, uint32 minBinIndex, uint32 maxBinIndex, double rangeMin, double rangeMax, double xStart, double xEnd, double yStart, double yEnd, bool horizontalView, bool drawFromRight) { if (spectrum == NULL) return; @@ -925,68 +1005,141 @@ void OpenGLWidget2DHelpers::RenderSpectrumChart(OpenGLWidgetCallback* callback, const double xRange = xEnd - xStart; const double yRange = yEnd - yStart; - Color color2 = FromQtColor( ToQColor(color).lighter(110) ); - Color color1 = FromQtColor( ToQColor(color).darker(170) ); - Color adjustedColor2; + if (horizontalView == false) { + Color color2 = FromQtColor( ToQColor(color).lighter(110) ); + Color color1 = FromQtColor( ToQColor(color).darker(170) ); + Color adjustedColor2; - const double binWidth = ((xRange)/(double)numBins)-1.0; + const double binWidth = ((xRange)/(double)numBins)-1.0; - for (uint32 i=1; i<=rangeBins; ++i) - { - const uint32 lastBinIndex = minBinIndex + i - 1; - //const uint32 binIndex = minBinIndex + i; + for (uint32 i=1; i<=rangeBins; ++i) + { + const uint32 lastBinIndex = minBinIndex + i - 1; + + const double lastX = ClampedRemapRange(lastBinIndex, minBinIndex, maxBinIndex, xStart, xEnd); + const double lastY = ClampedRemapRange(spectrum->GetBin(lastBinIndex), rangeMin, rangeMax, yStart, yEnd); + + //const double x = ClampedRemapRange( binIndex, minBinIndex, endBinIndex, xStart, xEnd ); + //const double y = ClampedRemapRange( spectrum->GetBin(binIndex), rangeMin, rangeMax, yStart, yEnd ); - const double lastX = ClampedRemapRange(lastBinIndex, minBinIndex, maxBinIndex, xStart, xEnd); - const double lastY = ClampedRemapRange(spectrum->GetBin(lastBinIndex), rangeMin, rangeMax, yStart, yEnd); + float colorInterpolate = ClampedRemapRange(spectrum->GetBin(lastBinIndex), rangeMin, rangeMax, 0.0, 1.0); + adjustedColor2 = LinearInterpolate( color1, color2, colorInterpolate ); - //const double x = ClampedRemapRange( binIndex, minBinIndex, endBinIndex, xStart, xEnd ); - //const double y = ClampedRemapRange( spectrum->GetBin(binIndex), rangeMin, rangeMax, yStart, yEnd ); + OpenGLWidgetCallback::Rect rect; - float colorInterpolate = ClampedRemapRange(spectrum->GetBin(lastBinIndex), rangeMin, rangeMax, 0.0, 1.0); - adjustedColor2 = LinearInterpolate( color1, color2, colorInterpolate ); + rect.mColor1 = rect.mColor2 = color1; + rect.mColor3 = rect.mColor4 = adjustedColor2; - OpenGLWidgetCallback::Rect rect; + const double binHeight = yRange+lastY; - rect.mColor1 = rect.mColor2 = color1; - rect.mColor3 = rect.mColor4 = adjustedColor2; + if (binWidth > 1) + { + // 1 + rect.mX1 = lastX; + rect.mY1 = yStart; + + // 2 + rect.mX2 = lastX + binWidth; + rect.mY2 = yStart; + + // 3 + rect.mX3 = lastX + binWidth; + rect.mY3 = yStart + binHeight; - const double binHeight = yRange+lastY; + // 4 + rect.mX4 = lastX; + rect.mY4 = yStart + binHeight; - if (binWidth > 1) + callback->AddRect( rect ); + } + else + callback->AddLine( lastX, yStart, color1, lastX, yStart+binHeight, adjustedColor2 ); + } + } else { + const double binWidth = ((yRange)/(double)numBins); + + for (uint32 i = 1; i <= rangeBins; ++i) { - /* - const double xFrom = lastX; - const double yFrom = yStart; - const double xTo = lastX + binWidth; - const double yTo = yStart + binHeight; - - // draw rectangle using a batch of lines - for (double x = xFrom; x < xTo; x += 1.0) - callback->AddLine( x, yFrom, color1, x, yTo, adjustedColor2 ); - */ - - // 1 - rect.mX1 = lastX; - rect.mY1 = yStart; - - // 2 - rect.mX2 = lastX + binWidth; - rect.mY2 = yStart; - - // 3 - rect.mX3 = lastX + binWidth; - rect.mY3 = yStart + binHeight; - - // 4 - rect.mX4 = lastX; - rect.mY4 = yStart + binHeight; - - callback->AddRect( rect ); + + const uint32 lastBinIndex = minBinIndex + i - 1; + + // Assign the color based on the frequency range + Color barColor = barColorByFrequency(lastBinIndex); + + // Map bin index to X position (this will represent the height of the bar) + const double lastX = ClampedRemapRange(spectrum->GetBin(lastBinIndex), rangeMin, rangeMax, xStart, xEnd); + + // Map bin value to Y position (this will represent the vertical placement) + const double lastY = ClampedRemapRange(lastBinIndex, minBinIndex, maxBinIndex, yStart, yEnd); + + OpenGLWidgetCallback::Rect rect; + + rect.mColor1 = rect.mColor4 = barColor; // Bottom-left and Top-left + rect.mColor2 = rect.mColor3 = barColor; // Bottom-right and Top-right + + if (drawFromRight) { + // Bottom-left + rect.mX1 = std::max(xStart, xEnd - lastX); + rect.mY1 = lastY; + + // Bottom-right + rect.mX2 = xEnd; + rect.mY2 = lastY; + + // Top-right + rect.mX3 = xEnd; + rect.mY3 = lastY + binWidth; + + // Top-left + rect.mX4 = std::max(xStart, xEnd - lastX); + rect.mY4 = lastY + binWidth; + } else { + // Bottom-left + rect.mX1 = xStart; + rect.mY1 = lastY; + + // Bottom-right + rect.mX2 = std::min(xStart + lastX, xEnd); + rect.mY2 = lastY; + + // Top-right + rect.mX3 = std::min(xStart + lastX, xEnd); + rect.mY3 = lastY + binWidth; + + // Top-left + rect.mX4 = xStart; + rect.mY4 = lastY + binWidth; + } + + callback->AddRect(rect); } - else - callback->AddLine( lastX, yStart, color1, lastX, yStart+binHeight, adjustedColor2 ); } callback->RenderLines(); callback->RenderRects(); } + +// color selector for bars rendering +Core::Color OpenGLWidget2DHelpers::barColorByFrequency (uint32 binIndex) +{ + // Assign the color based on the frequency range + Color barColor; + if (binIndex >= 0 && binIndex <= 3) + barColor = Color(1.0f, 0.0f, 0.0f); // Red + else if (binIndex >= 3 && binIndex <= 8) + barColor = Color(1.0f, 0.65f, 0.0f); // Orange + else if (binIndex >= 8 && binIndex <= 12) + barColor = Color(1.0f, 1.0f, 0.0f); // Yellow + else if (binIndex >= 12 && binIndex <= 15) + barColor = Color(0.25f, 0.88f, 0.82f); // Turquoise + else if (binIndex >= 15 && binIndex <= 23) + barColor = Color(0.6f, 0.8f, 1.0f); // Blue + else if (binIndex >= 23 && binIndex <= 38) + barColor = Color(0.75f, 0.5f, 1.0f); // Purple + else if (binIndex >= 38 && binIndex <= 60) + barColor = Color(1.0f, 0.75f, 0.8f); // Pink + else + barColor = Color(0.75f, 0.75f, 0.75f); // Default to light gray if out of range + + return barColor; +} \ No newline at end of file diff --git a/src/Studio/Rendering/OpenGLWidget2DHelpers.h b/src/Studio/Rendering/OpenGLWidget2DHelpers.h index 09d1a2ee..1a918778 100644 --- a/src/Studio/Rendering/OpenGLWidget2DHelpers.h +++ b/src/Studio/Rendering/OpenGLWidget2DHelpers.h @@ -51,10 +51,10 @@ class OpenGLWidget2DHelpers uint32 mNumYSplits; uint32 mNumYSubSplits; }; - static void RenderGrid(OpenGLWidgetCallback* callback, GridInfo* gridInfo, const Core::Color& mainColor, const Core::Color& subColor, double xStart, double xEnd, double yStart, double yEnd); - static void RenderEquallySpacedYLabels(OpenGLWidgetCallback* callback, Core::String& tempString, const QColor& textColor, double minValue, double maxValue, uint32 numSplits, double x, double y, double width, double height); - static void RenderEquallySpacedXLabels(OpenGLWidgetCallback* callback, Core::String& tempString, const char* unit, const QColor& textColor, double minValue, double maxValue, uint32 numSplits, double x, double y, double width, double height); + static void RenderGrid(OpenGLWidgetCallback* callback, GridInfo* gridInfo, const Core::Color& mainColor, const Core::Color& subColor, double xStart, double xEnd, double yStart, double yEnd); + static void RenderEquallySpacedYLabels(OpenGLWidgetCallback* callback, Core::String& tempString, const QColor& textColor, double minValue, double maxValue, uint32 numSplits, double x, double y, double width, double height, bool horizontalView = false, bool drawFromRight = false, uint32 numSubsplits = 0); + static void RenderEquallySpacedXLabels(OpenGLWidgetCallback* callback, Core::String& tempString, const char* unit, const QColor& textColor, double minValue, double maxValue, uint32 numSplits, double x, double y, double width, double height, bool horizontalView = false, bool drawFromRight = false); // // chart rendering // @@ -83,16 +83,17 @@ class OpenGLWidget2DHelpers // // Timeline Rendering // NOTE: used for feedback plugin - static void RenderTimeline(OpenGLWidgetCallback* callback, const Core::Color& color, double timeRange, double maxTime, double x, double y, double width, double height, Core::String& tempString); + static void RenderTimeline(OpenGLWidgetCallback* callback, const Core::Color& color, double timeRange, double maxTime, double x, double y, double width, double height, Core::String& tempString, bool scaleInMins = false); // // Spectrum Rendering // NOTE: used for spectrum plugin static void RenderSpectrum(OpenGLWidgetCallback* callback, Spectrum* spectrum, const Core::Color& color, uint32 minBinIndex, uint32 maxBinIndex, double rangeMin, double rangeMax, double xStart, double xEnd, double yStart, double yEnd); - static void RenderSpectrumChart(OpenGLWidgetCallback* callback, Spectrum* spectrum, const Core::Color& color, uint32 minBinIndex, uint32 maxBinIndex, double rangeMin, double rangeMax, double xStart, double xEnd, double yStart, double yEnd); + static void RenderSpectrumChart(OpenGLWidgetCallback* callback, Spectrum* spectrum, const Core::Color& color, uint32 minBinIndex, uint32 maxBinIndex, double rangeMin, double rangeMax, double xStart, double xEnd, double yStart, double yEnd, bool horizontalView, bool drawFromRight); private: static RenderSampleFunction SelectSampleRenderFunction(EChartRenderStyle style, bool clipped = false); + static Core::Color barColorByFrequency(uint32 binIndex); }; diff --git a/src/Studio/Widgets/ChannelMultiSelectionWidget.cpp b/src/Studio/Widgets/ChannelMultiSelectionWidget.cpp index 94982533..c68339ed 100644 --- a/src/Studio/Widgets/ChannelMultiSelectionWidget.cpp +++ b/src/Studio/Widgets/ChannelMultiSelectionWidget.cpp @@ -238,7 +238,7 @@ void ChannelMultiSelectionWidget::ClearUsedChannels() void ChannelMultiSelectionWidget::SetChecked(Channel* channel, bool checked) { - const uint32 channelIndex = mSelectedChannels.Find(channel); + const uint32 channelIndex = mAvailableChannels.Find(channel); if (channelIndex != CORE_INVALIDINDEX32) SetChecked(channelIndex, checked); } @@ -256,4 +256,4 @@ void ChannelMultiSelectionWidget::SetVisible(uint32 index, bool visible) return; mChannelMultiCheckbox->GetCheckbox(index)->setVisible(visible); -} +} \ No newline at end of file From 8f1491bdec0ca1c3bdef4efa9ff7471b692098dd Mon Sep 17 00:00:00 2001 From: Julius Rudolph Date: Thu, 13 Feb 2025 09:41:41 +0000 Subject: [PATCH 6/6] Release 1.7.5 --- .github/workflows/build-android.yml | 2 +- .github/workflows/build-ios.yml | 2 +- .github/workflows/build-linux.yml | 6 +++--- .github/workflows/build-osx.yml | 10 +++++----- .github/workflows/build-win.yml | 8 ++++---- src/Engine/Version.h | 2 +- src/QtBase/Version.h | 2 +- src/Studio/Version.h | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index a1447e7f..85464e13 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -87,7 +87,7 @@ jobs: # Upload Engine - name: Upload Engine - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Engine-${{ matrix.arch }}-${{ matrix.branding }} (${{ matrix.os }}) path: | diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 2c025ebc..d13d9465 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -69,7 +69,7 @@ jobs: # Upload Engine - name: Upload Engine - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Engine-${{ matrix.arch }}-${{ matrix.branding }} (${{ matrix.os }}) path: | diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 4b71875c..7c660806 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -28,7 +28,7 @@ jobs: touch repository.tar.zst tar -I "zstd -T0 --ultra -10" --exclude=repository.tar.zst --exclude=.git -cf repository.tar.zst . - name: Upload - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: retention-days: 1 name: Repository @@ -238,14 +238,14 @@ jobs: # Upload Studio - name: Upload Studio - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Packages (${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.branding }}) path: ./dist/${{ matrix.os }}/*.deb # Upload Engine - name: Upload Engine - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Engine (${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.branding }}) path: | diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml index 8c97a7d7..3555e9dc 100644 --- a/.github/workflows/build-osx.yml +++ b/.github/workflows/build-osx.yml @@ -85,7 +85,7 @@ jobs: # Upload - name: Upload - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Binaries (${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.branding }}) path: | @@ -94,7 +94,7 @@ jobs: # Upload lto.o - name: Upload lto.o - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: retention-days: 1 name: lto.o (${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.branding }}) @@ -102,7 +102,7 @@ jobs: # Upload Engine - name: Upload Engine - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Engine (${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.branding }}) path: | @@ -184,7 +184,7 @@ jobs: # Upload - name: Upload - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Packages (${{ matrix.os }}-${{ matrix.branding }}) path: ./dist/osx-10.15/*.pkg @@ -272,7 +272,7 @@ jobs: # Upload - name: Upload - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Packages Store (${{ matrix.os }}-${{ matrix.branding }}) path: ./dist/osx-10.15/*.pkg diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml index 30535aa5..f6e7c6eb 100644 --- a/.github/workflows/build-win.yml +++ b/.github/workflows/build-win.yml @@ -107,7 +107,7 @@ jobs: # Upload Studio - name: Upload Studio - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Binaries (${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.branding }}) path: | @@ -117,7 +117,7 @@ jobs: # Upload Engine - name: Upload Engine - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Engine (${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.branding }}) path: | @@ -219,7 +219,7 @@ jobs: # Upload MSI - name: Upload MSI - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Packages MSI (${{ matrix.os }}-${{ matrix.branding }}) path: | @@ -227,7 +227,7 @@ jobs: # Upload Packages - name: Upload App - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Packages App (${{ matrix.os }}-${{ matrix.branding }}) path: | diff --git a/src/Engine/Version.h b/src/Engine/Version.h index d530f3bc..5422dd6c 100644 --- a/src/Engine/Version.h +++ b/src/Engine/Version.h @@ -26,6 +26,6 @@ #define NEUROMORE_ENGINE_VERSION_MAJOR 1 #define NEUROMORE_ENGINE_VERSION_MINOR 7 -#define NEUROMORE_ENGINE_VERSION_PATCH 4 +#define NEUROMORE_ENGINE_VERSION_PATCH 5 #endif diff --git a/src/QtBase/Version.h b/src/QtBase/Version.h index a9b53d1b..4fc79d2d 100644 --- a/src/QtBase/Version.h +++ b/src/QtBase/Version.h @@ -9,6 +9,6 @@ #define NEUROMORE_QTBASE_VERSION_MAJOR 1 #define NEUROMORE_QTBASE_VERSION_MINOR 7 -#define NEUROMORE_QTBASE_VERSION_PATCH 4 +#define NEUROMORE_QTBASE_VERSION_PATCH 5 #endif \ No newline at end of file diff --git a/src/Studio/Version.h b/src/Studio/Version.h index 3ed11bb4..49099f74 100644 --- a/src/Studio/Version.h +++ b/src/Studio/Version.h @@ -26,7 +26,7 @@ #define NEUROMORE_STUDIO_VERSION_MAJOR 1 #define NEUROMORE_STUDIO_VERSION_MINOR 7 -#define NEUROMORE_STUDIO_VERSION_PATCH 4 +#define NEUROMORE_STUDIO_VERSION_PATCH 5 // Macros