From 26c2be24678c634328faae9a523db8e96a38b0df Mon Sep 17 00:00:00 2001 From: Tobias Baumann Date: Thu, 10 Feb 2022 17:58:02 +0000 Subject: [PATCH] =?UTF-8?q?Dateien=20hochladen=20nach=20=E2=80=9Esrc/Tacti?= =?UTF-8?q?leVisualization=E2=80=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/TactileVisualization/TactileMenu.cpp | 85 ++ src/TactileVisualization/TactileMenu.h | 53 ++ ...pCBoxAlgorithmP300TactileVisualization.cpp | 764 ++++++++++++++++++ ...ovpCBoxAlgorithmP300TactileVisualization.h | 206 +++++ 4 files changed, 1108 insertions(+) create mode 100644 src/TactileVisualization/TactileMenu.cpp create mode 100644 src/TactileVisualization/TactileMenu.h create mode 100644 src/TactileVisualization/ovpCBoxAlgorithmP300TactileVisualization.cpp create mode 100644 src/TactileVisualization/ovpCBoxAlgorithmP300TactileVisualization.h diff --git a/src/TactileVisualization/TactileMenu.cpp b/src/TactileVisualization/TactileMenu.cpp new file mode 100644 index 0000000..cdf38f6 --- /dev/null +++ b/src/TactileVisualization/TactileMenu.cpp @@ -0,0 +1,85 @@ +///------------------------------------------------------------------------------------------------- +/// +/// \file TactileMenu.cpp +/// \brief Definitions for class TactileMenu +/// \author Tobias Baumann (TH Nuernberg). +/// \version 1.0. +/// \date Mon Feb 10 17:11:34 2022. +/// \copyright GNU Affero General Public License v3.0. +/// +///------------------------------------------------------------------------------------------------- + +//includes +#include "TactileMenu.h" + +#include + +namespace OpenViBE { +namespace Plugins { +namespace Tactilebci { + +//TactileMenu Memberfunctions +TactileMenu::TactileMenu(char* label0, char* label1, char* label2, char* label3, char* label4, char* label5, GtkBuilder* maininterface) +{ + m_LabelText[0] = label0; + m_LabelText[1] = label1; + m_LabelText[2] = label2; + m_LabelText[3] = label3; + m_LabelText[4] = label4; + m_LabelText[5] = label5; + + m_Maininterface = maininterface; + + for(int i = 0; i < 6; i++) + { + m_SubMenu[i] = this; + m_SubRoutine[i] = nullptr; + m_Label[i] = GTK_LABEL(gtk_builder_get_object(maininterface, "label-" + to_string(i)); + } +} + +void TactileMenu::call_SubRoutine(int i) +{ + if(m_SubRoutine[i] != nullptr) + { + (*m_SubRoutine[i])(); + } +} + +char* TactileMenu::get_LabelText(int i) +{ + return(m_LabelText[i]); +} + +TactileMenu* TactileMenu::get_SubMenu(int i) +{ + return(m_SubMenu[i]); +} + +void TactileMenu::set_SubRoutine(int i, void(*subroutine)(void)) +{ + m_SubRoutine[i] = subroutine; +} + +void TactileMenu::set_SubMenu(int i, TactileMenu* submenu) +{ + m_SubMenu[i] = submenu; +} + +void TactileMenu::reloadui(void) +{ + for(uint64_t i = 0; i < 6; i++) + { + gtk_label_set_text(m_Label[i], m_LabelText[i]); + } +} + + +//Subroutine Functions +void dummysubroutine(void) +{ +} + +} // namespace Tactilebci +} // namespace Plugins +} // namespace OpenViBE \ No newline at end of file diff --git a/src/TactileVisualization/TactileMenu.h b/src/TactileVisualization/TactileMenu.h new file mode 100644 index 0000000..6b1f754 --- /dev/null +++ b/src/TactileVisualization/TactileMenu.h @@ -0,0 +1,53 @@ +///------------------------------------------------------------------------------------------------- +/// +/// \file TactileMenu.h +/// \brief Class for the Menues of the Tactile P300 System +/// \author Tobias Baumann (TH Nuernberg). +/// \version 1.0. +/// \date Mon Feb 10 17:10:32 2022. +/// \copyright GNU Affero General Public License v3.0. +/// +///------------------------------------------------------------------------------------------------- + +//includes +#pragma once + +#include "../ovp_defines.h" + +#include +#include +#include + +namespace OpenViBE { +namespace Plugins { +namespace Tactilebci { + +//Class TactileMenu +class TactileMenu +{ + private: + char* m_LabelText[6]; + TactileMenu* m_SubMenu[6]; + void (*m_SubRoutine[6])(void); + GtkLabel* m_Label[6]; + GtkBuilder* m_Maininterface; + + public: + TactileMenu(char* label0, char* label1, char* label2, char* label3, char* label4, char* label5, GtkBuilder* maininterface); + void call_SubRoutine(int i); + char* get_LabelText(int i); + TactileMenu* get_SubMenu(int i); + + void set_SubRoutine(int i, void(*subroutine)(void)); + void set_SubMenu(int i, TactileMenu* submenu); + + void reloadui(void); +}; + + +//Subroutine declarations +void dummysubroutine(void); + +} // namespace Tactilebci +} // namespace Plugins +} // namespace OpenViBE \ No newline at end of file diff --git a/src/TactileVisualization/ovpCBoxAlgorithmP300TactileVisualization.cpp b/src/TactileVisualization/ovpCBoxAlgorithmP300TactileVisualization.cpp new file mode 100644 index 0000000..1ae6ee1 --- /dev/null +++ b/src/TactileVisualization/ovpCBoxAlgorithmP300TactileVisualization.cpp @@ -0,0 +1,764 @@ +///------------------------------------------------------------------------------------------------- +/// +/// \file ovpCBoxAlgorithmP300TactileVisualization.cpp +/// \brief Functions of the Class P300TactileVisualization. !!This is a modification of the P300 Speller Visualization Box!! +/// \author Tobias Baumann (TH Nuernberg). +/// \version 1.0. +/// \date Mon Feb 04 12:43:53 2022. +/// \copyright GNU Affero General Public License v3.0 +/// +///------------------------------------------------------------------------------------------------- + +//includes +#include "ovpCBoxAlgorithmP300TactileVisualization.h" +#include + +#include +#include +#include +#include + +namespace OpenViBE { +namespace Plugins { +namespace Tactilebci { + +static void ToggleButtonShowHideCB(GtkToggleToolButton* button, gpointer data) +{ + if (gtk_toggle_tool_button_get_active(button)) { gtk_widget_show(GTK_WIDGET(data)); } + else { gtk_widget_hide(GTK_WIDGET(data)); } +} + +// This callback flushes all accumulated stimulations to the TCP Tagging +// after the rendering has completed. +static gboolean FlushCB(gpointer data) +{ + (static_cast(data))->flushQueue(); + + return false; // Only run once +} + +bool CBoxAlgorithmP300TactileVisualization::initialize() +{ + const Kernel::IBox& boxContext = this->getStaticBoxContext(); + + m_mainWidgetInterface = nullptr; + m_toolbarWidgetInterface = nullptr; + m_flashFontDesc = nullptr; + m_noFlashFontDesc = nullptr; + m_targetFontDesc = nullptr; + m_selectedFontDesc = nullptr; + + // ---------------------------------------------------------------------------------------------------------------------------------------------------------- + + m_interfaceFilename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0); + m_rowStimulationBase = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1); + m_columnStimulationBase = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2); + + m_flashBgColor = CGdkcolorAutoCast(boxContext, this->getConfigurationManager(), 3); + m_flashFgColor = CGdkcolorAutoCast(boxContext, this->getConfigurationManager(), 4); + m_flashFontSize = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 5); + m_noFlashBgColor = CGdkcolorAutoCast(boxContext, this->getConfigurationManager(), 6); + m_noFlashFgColor = CGdkcolorAutoCast(boxContext, this->getConfigurationManager(), 7); + m_noFlashFontSize = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 8); + m_targetBgColor = CGdkcolorAutoCast(boxContext, this->getConfigurationManager(), 9); + m_targetFgColor = CGdkcolorAutoCast(boxContext, this->getConfigurationManager(), 10); + m_targetFontSize = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 11); + m_selectedBgColor = CGdkcolorAutoCast(boxContext, this->getConfigurationManager(), 12); + m_selectedFgColor = CGdkcolorAutoCast(boxContext, this->getConfigurationManager(), 13); + m_selectedFontSize = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 14); + + // ---------------------------------------------------------------------------------------------------------------------------------------------------------- + + m_sequenceStimulationDecoder = &this->getAlgorithmManager().getAlgorithm( + this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationDecoder)); + m_sequenceStimulationDecoder->initialize(); + + m_targetStimulationDecoder = &this->getAlgorithmManager().getAlgorithm( + this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationDecoder)); + m_targetStimulationDecoder->initialize(); + + m_targetFlaggingStimulationEncoder = &this->getAlgorithmManager().getAlgorithm( + this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationEncoder)); + m_targetFlaggingStimulationEncoder->initialize(); + + m_rowSelectionStimulationDecoder = &this->getAlgorithmManager().getAlgorithm( + this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationDecoder)); + m_rowSelectionStimulationDecoder->initialize(); + + m_columnSelectionStimulationDecoder = &this->getAlgorithmManager().getAlgorithm( + this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationDecoder)); + m_columnSelectionStimulationDecoder->initialize(); + + m_sequenceMemoryBuffer.initialize( + m_sequenceStimulationDecoder->getInputParameter(OVP_GD_Algorithm_StimulationDecoder_InputParameterId_MemoryBufferToDecode)); + m_sequenceStimulationSet.initialize( + m_sequenceStimulationDecoder->getOutputParameter(OVP_GD_Algorithm_StimulationDecoder_OutputParameterId_StimulationSet)); + + m_targetMemoryBuffer.initialize( + m_targetStimulationDecoder->getInputParameter(OVP_GD_Algorithm_StimulationDecoder_InputParameterId_MemoryBufferToDecode)); + m_targetStimulationSet.initialize( + m_targetStimulationDecoder->getOutputParameter(OVP_GD_Algorithm_StimulationDecoder_OutputParameterId_StimulationSet)); + + m_targetFlaggingStimulationSet.initialize( + m_targetFlaggingStimulationEncoder->getInputParameter(OVP_GD_Algorithm_StimulationEncoder_InputParameterId_StimulationSet)); + m_targetFlaggingMemoryBuffer.initialize( + m_targetFlaggingStimulationEncoder->getOutputParameter(OVP_GD_Algorithm_StimulationEncoder_OutputParameterId_EncodedMemoryBuffer)); + + m_lastTime = 0; + + m_stimulusSender = nullptr; + + m_idleFuncTag = 0; + m_stimuliQueue.clear(); + + m_mainWidgetInterface = gtk_builder_new(); // glade_xml_new(m_interfaceFilename.toASCIIString(), "p300-speller-main", nullptr); + if (!gtk_builder_add_from_file(m_mainWidgetInterface, m_interfaceFilename.toASCIIString(), nullptr)) + { + this->getLogManager() << Kernel::LogLevel_ImportantWarning << "Could not load interface file [" << m_interfaceFilename << "]\n"; + this->getLogManager() << Kernel::LogLevel_ImportantWarning << + "The file may be missing. However, the interface files now use gtk-builder instead of glade. Did you update your files ?\n"; + return false; + } + + m_toolbarWidgetInterface = gtk_builder_new(); // glade_xml_new(m_interfaceFilename.toASCIIString(), "p300-speller-toolbar", nullptr); + gtk_builder_add_from_file(m_toolbarWidgetInterface, m_interfaceFilename.toASCIIString(), nullptr); + + m_mainWindow = GTK_WIDGET(gtk_builder_get_object(m_mainWidgetInterface, "p300-speller-main")); + m_toolbarWidget = GTK_WIDGET(gtk_builder_get_object(m_toolbarWidgetInterface, "p300-speller-toolbar")); + m_table = GTK_TABLE(gtk_builder_get_object(m_mainWidgetInterface, "p300-speller-table")); + m_result = GTK_LABEL(gtk_builder_get_object(m_mainWidgetInterface, "label-result")); + m_target = GTK_LABEL(gtk_builder_get_object(m_mainWidgetInterface, "label-target")); + + //TactileMenu initialization + TactileMenu CBoxAlgorithmP300TactileVisualization::m_MainMenu("Ja", "Nein", "Pflege", "Hilfe", "Geräte", "Optionen", m_mainWidgetInterface); + TactileMenu CBoxAlgorithmP300TactileVisualization::m_CareMenu("Ja", "Nein", "Hunger", "Lage", "Müdigkeit", "Hauptmenü", m_mainWidgetInterface); + TactileMenu CBoxAlgorithmP300TactileVisualization::m_HelpMenu("Ja", "Nein", "Schmerzen", "Atemnot", "Anderes", "Hauptmenü", m_mainWidgetInterface); + TactileMenu CBoxAlgorithmP300TactileVisualization::m_DevicesMenu("Ja", "Nein", "Atemgerät", "Rollstuhl", "Computer", "Hauptmenü", m_mainWidgetInterface); + TactileMenu CBoxAlgorithmP300TactileVisualization::m_FunctionsMenu("Ja", "Nein", "befehl1", "befehl2", "befehl3", "Hauptmenü", m_mainWidgetInterface); + + + + m_CurrentMenu = &m_MainMenu; + m_CurrentMenu->reloadui(); +//Grenze------------------------------------------------------------------------------------- + gtk_builder_connect_signals(m_mainWidgetInterface, nullptr); + gtk_builder_connect_signals(m_toolbarWidgetInterface, nullptr); + + g_signal_connect(gtk_builder_get_object(m_toolbarWidgetInterface, "toolbutton-show_target_text"), "toggled", G_CALLBACK(ToggleButtonShowHideCB), + gtk_builder_get_object(m_mainWidgetInterface, "label-target")); + g_signal_connect(gtk_builder_get_object(m_toolbarWidgetInterface, "toolbutton-show_target_text"), "toggled", G_CALLBACK(ToggleButtonShowHideCB), + gtk_builder_get_object(m_mainWidgetInterface, "label-target-title")); + g_signal_connect(gtk_builder_get_object(m_toolbarWidgetInterface, "toolbutton-show_result_text"), "toggled", G_CALLBACK(ToggleButtonShowHideCB), + gtk_builder_get_object(m_mainWidgetInterface, "label-result")); + g_signal_connect(gtk_builder_get_object(m_toolbarWidgetInterface, "toolbutton-show_result_text"), "toggled", G_CALLBACK(ToggleButtonShowHideCB), + gtk_builder_get_object(m_mainWidgetInterface, "label-result-title")); + + m_visualizationCtx = dynamic_cast(this->createPluginObject( + OVP_ClassId_Plugin_VisualizationCtx)); + m_visualizationCtx->setWidget(*this, m_mainWindow); + m_visualizationCtx->setToolbar(*this, m_toolbarWidget); + + guint nRow = 0, nCol = 0; + g_object_get(m_table, "n-rows", &nRow, nullptr); + g_object_get(m_table, "n-columns", &nCol, nullptr); + + m_nRow = nRow; + m_nCol = nCol; + + PangoFontDescription* maxFontDesc = pango_font_description_copy(pango_context_get_font_description(gtk_widget_get_pango_context(m_mainWindow))); + m_flashFontDesc = pango_font_description_copy(pango_context_get_font_description(gtk_widget_get_pango_context(m_mainWindow))); + m_noFlashFontDesc = pango_font_description_copy(pango_context_get_font_description(gtk_widget_get_pango_context(m_mainWindow))); + m_targetFontDesc = pango_font_description_copy(pango_context_get_font_description(gtk_widget_get_pango_context(m_mainWindow))); + m_selectedFontDesc = pango_font_description_copy(pango_context_get_font_description(gtk_widget_get_pango_context(m_mainWindow))); + + uint64_t maxSize = 0; + maxSize = std::max(maxSize, m_flashFontSize); + maxSize = std::max(maxSize, m_noFlashFontSize); + maxSize = std::max(maxSize, m_targetFontSize); + maxSize = std::max(maxSize, m_selectedFontSize); + + pango_font_description_set_size(maxFontDesc, gint(maxSize * PANGO_SCALE)); + pango_font_description_set_size(m_flashFontDesc, gint(m_flashFontSize * PANGO_SCALE)); + pango_font_description_set_size(m_noFlashFontDesc, gint(m_noFlashFontSize * PANGO_SCALE)); + pango_font_description_set_size(m_targetFontDesc, gint(m_targetFontSize * PANGO_SCALE)); + pango_font_description_set_size(m_selectedFontDesc, gint(m_selectedFontSize * PANGO_SCALE)); + + this->cacheBuildFromTable(m_table); + this->cacheForEach(&CBoxAlgorithmP300TactileVisualization::cacheChangeBackgroundCB, &m_noFlashBgColor); + this->cacheForEach(&CBoxAlgorithmP300TactileVisualization::cacheChangeForegroundCB, &m_noFlashFgColor); + this->cacheForEach(&CBoxAlgorithmP300TactileVisualization::cacheChangeFontCB, maxFontDesc); + + pango_font_description_free(maxFontDesc); + + m_lastTargetRow = -1; + m_lastTargetCol = -1; + m_targetRow = -1; + m_targetCol = -1; + m_selectedRow = -1; + m_selectedCol = -1; + + m_stimulusSender = TCPTagging::CreateStimulusSender(); + + if (!m_stimulusSender->connect("localhost", "15361")) + { + this->getLogManager() << Kernel::LogLevel_Warning << "Unable to connect to AS TCP Tagging, stimuli wont be forwarded.\n"; + } + + m_tableInitialized = false; + + return true; +} + +bool CBoxAlgorithmP300TactileVisualization::uninitialize() +{ + if (m_idleFuncTag) + { + m_stimuliQueue.clear(); + g_source_remove(m_idleFuncTag); + m_idleFuncTag = 0; + } + + if (m_stimulusSender) + { + delete m_stimulusSender; + m_stimulusSender = nullptr; + } + + if (m_selectedFontDesc) + { + pango_font_description_free(m_selectedFontDesc); + m_selectedFontDesc = nullptr; + } + + if (m_targetFontDesc) + { + pango_font_description_free(m_targetFontDesc); + m_targetFontDesc = nullptr; + } + + if (m_noFlashFontDesc) + { + pango_font_description_free(m_noFlashFontDesc); + m_noFlashFontDesc = nullptr; + } + + if (m_flashFontDesc) + { + pango_font_description_free(m_flashFontDesc); + m_flashFontDesc = nullptr; + } + + if (m_toolbarWidgetInterface) + { + g_object_unref(m_toolbarWidgetInterface); + m_toolbarWidgetInterface = nullptr; + } + + if (m_mainWidgetInterface) + { + g_object_unref(m_mainWidgetInterface); + m_mainWidgetInterface = nullptr; + } + + m_targetFlaggingStimulationSet.uninitialize(); + m_targetFlaggingMemoryBuffer.uninitialize(); + + m_targetStimulationSet.uninitialize(); + m_targetMemoryBuffer.uninitialize(); + + m_sequenceStimulationSet.uninitialize(); + m_sequenceMemoryBuffer.uninitialize(); + + if (m_columnSelectionStimulationDecoder) + { + m_columnSelectionStimulationDecoder->uninitialize(); + this->getAlgorithmManager().releaseAlgorithm(*m_columnSelectionStimulationDecoder); + m_columnSelectionStimulationDecoder = nullptr; + } + + if (m_rowSelectionStimulationDecoder) + { + m_rowSelectionStimulationDecoder->uninitialize(); + this->getAlgorithmManager().releaseAlgorithm(*m_rowSelectionStimulationDecoder); + m_rowSelectionStimulationDecoder = nullptr; + } + + if (m_targetFlaggingStimulationEncoder) + { + m_targetFlaggingStimulationEncoder->uninitialize(); + this->getAlgorithmManager().releaseAlgorithm(*m_targetFlaggingStimulationEncoder); + m_targetFlaggingStimulationEncoder = nullptr; + } + + if (m_targetStimulationDecoder) + { + m_targetStimulationDecoder->uninitialize(); + this->getAlgorithmManager().releaseAlgorithm(*m_targetStimulationDecoder); + m_targetStimulationDecoder = nullptr; + } + + if (m_sequenceStimulationDecoder) + { + m_sequenceStimulationDecoder->uninitialize(); + this->getAlgorithmManager().releaseAlgorithm(*m_sequenceStimulationDecoder); + m_sequenceStimulationDecoder = nullptr; + } + + if (m_visualizationCtx) + { + this->releasePluginObject(m_visualizationCtx); + m_sequenceStimulationDecoder = nullptr; + } + + return true; +} + +bool CBoxAlgorithmP300TactileVisualization::processInput(const size_t /*index*/) +{ + this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess(); + + if (!m_tableInitialized) + { + this->cacheForEach(&CBoxAlgorithmP300TactileVisualization::cacheChangeBackgroundCB, &m_noFlashBgColor); + this->cacheForEach(&CBoxAlgorithmP300TactileVisualization::cacheChangeForegroundCB, &m_noFlashFgColor); + this->cacheForEach(&CBoxAlgorithmP300TactileVisualization::cacheChangeFontCB, m_noFlashFontDesc); + m_tableInitialized = true; + } + + return true; +} + +bool CBoxAlgorithmP300TactileVisualization::process() +{ + Kernel::IBoxIO& boxContext = this->getDynamicBoxContext(); + + // --- Sequence stimulations + + for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i) + { + CStimulationSet flaggingStimulationSet; + + m_sequenceMemoryBuffer = boxContext.getInputChunk(0, i); + m_targetFlaggingStimulationSet = &flaggingStimulationSet; + m_targetFlaggingMemoryBuffer = boxContext.getOutputChunk(0); + + m_sequenceStimulationDecoder->process(); + + m_lastTime = boxContext.getInputChunkEndTime(0, i); + + if (m_sequenceStimulationDecoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedHeader)) + { + m_targetFlaggingStimulationEncoder->process(OVP_GD_Algorithm_StimulationEncoder_InputTriggerId_EncodeHeader); + } + + if (m_sequenceStimulationDecoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedBuffer)) + { + IStimulationSet* stimulationSet = m_sequenceStimulationSet; + for (size_t j = 0; j < stimulationSet->getStimulationCount(); ++j) + { + uint64_t id = stimulationSet->getStimulationIdentifier(j); + bool flash = false; + int row = -1; + int col = -1; + bool isTarget = false; + + if (id >= m_rowStimulationBase && id < m_rowStimulationBase + m_nRow) + { + row = int(id - m_rowStimulationBase); + flash = true; + isTarget = (row == m_lastTargetRow); + } + if (id >= m_columnStimulationBase && id < m_columnStimulationBase + m_nCol) + { + col = int(id - m_columnStimulationBase); + flash = true; + isTarget = (col == m_lastTargetCol); + } + if (id == OVTK_StimulationId_VisualStimulationStop) + { + this->getLogManager() << Kernel::LogLevel_Debug << "Received OVTK_StimulationId_VisualStimulationStop - resets grid\n"; + this->cacheForEach(&CBoxAlgorithmP300TactileVisualization::cacheChangeBackgroundCB, &m_noFlashBgColor); + this->cacheForEach(&CBoxAlgorithmP300TactileVisualization::cacheChangeForegroundCB, &m_noFlashFgColor); + this->cacheForEach(&CBoxAlgorithmP300TactileVisualization::cacheChangeFontCB, m_noFlashFontDesc); + } + if (id == OVTK_StimulationId_Reset) + { + gtk_label_set_text(m_target, ""); + gtk_label_set_text(m_result, ""); + } + + if (flash) + { + this->cacheForEachIf(row, col, &CBoxAlgorithmP300TactileVisualization::cacheChangeBackgroundCB, + &CBoxAlgorithmP300TactileVisualization::cacheChangeBackgroundCB, &m_flashBgColor, &m_noFlashBgColor); + this->cacheForEachIf(row, col, &CBoxAlgorithmP300TactileVisualization::cacheChangeForegroundCB, + &CBoxAlgorithmP300TactileVisualization::cacheChangeForegroundCB, &m_flashFgColor, &m_noFlashFgColor); + this->cacheForEachIf(row, col, &CBoxAlgorithmP300TactileVisualization::cacheChangeFontCB, + &CBoxAlgorithmP300TactileVisualization::cacheChangeFontCB, m_flashFontDesc, m_noFlashFontDesc); + + // We now know if this flash corresponds to the current target or not, merge this to the outgoing stimulation stream + if (isTarget) + { + m_stimuliQueue.push_back(OVTK_StimulationId_Target); + flaggingStimulationSet.appendStimulation(OVTK_StimulationId_Target, stimulationSet->getStimulationDate(j), 0); + } + else + { + m_stimuliQueue.push_back(OVTK_StimulationId_NonTarget); + flaggingStimulationSet.appendStimulation(OVTK_StimulationId_NonTarget, stimulationSet->getStimulationDate(j), 0); + } + } + + // Pass the stimulation to the server also as-is. If its a flash, it can be differentiated from a 'target' spec because + // its NOT between OVTK_StimulationId_RestStart and OVTK_StimulationId_RestStop stimuli in the generated P300 timeline. + m_stimuliQueue.push_back(id); + } + m_targetFlaggingStimulationEncoder->process(OVP_GD_Algorithm_StimulationEncoder_InputTriggerId_EncodeBuffer); + } + + if (m_sequenceStimulationDecoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedEnd)) + { + m_targetFlaggingStimulationEncoder->process(OVP_GD_Algorithm_StimulationEncoder_InputTriggerId_EncodeEnd); + } + + boxContext.markInputAsDeprecated(0, i); + boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i)); + } + + // --- Target stimulations + + for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i) + { + if (m_lastTime >= boxContext.getInputChunkStartTime(1, i)) + { + m_targetMemoryBuffer = boxContext.getInputChunk(1, i); + m_targetStimulationDecoder->process(); + + if (m_targetStimulationDecoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedHeader)) { } + + if (m_targetStimulationDecoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedBuffer)) + { + IStimulationSet* stimulationSet = m_targetStimulationSet; + for (size_t j = 0; j < stimulationSet->getStimulationCount(); ++j) + { + uint64_t id = stimulationSet->getStimulationIdentifier(j); + bool target = false; + if (id >= m_rowStimulationBase && id < m_rowStimulationBase + m_nRow) + { + this->getLogManager() << Kernel::LogLevel_Debug << "Received Target Row " << id << "\n"; + m_targetRow = int(id - m_rowStimulationBase); + target = true; + } + if (id >= m_columnStimulationBase && id < m_columnStimulationBase + m_nCol) + { + this->getLogManager() << Kernel::LogLevel_Debug << "Received Target Column " << id << "\n"; + m_targetCol = int(id - m_columnStimulationBase); + target = true; + } + + if (target && m_targetRow != -1 && m_targetCol != -1) + { + this->getLogManager() << Kernel::LogLevel_Debug << "Displays Target Cell\n"; + this->cacheForEachIf(m_targetRow, m_targetCol, &CBoxAlgorithmP300TactileVisualization::cacheChangeBackgroundCB, + &CBoxAlgorithmP300TactileVisualization::cacheChangeNullCB, &m_targetBgColor, nullptr); + this->cacheForEachIf(m_targetRow, m_targetCol, &CBoxAlgorithmP300TactileVisualization::cacheChangeForegroundCB, + &CBoxAlgorithmP300TactileVisualization::cacheChangeNullCB, &m_targetFgColor, nullptr); + this->cacheForEachIf(m_targetRow, m_targetCol, &CBoxAlgorithmP300TactileVisualization::cacheChangeFontCB, + &CBoxAlgorithmP300TactileVisualization::cacheChangeNullCB, m_targetFontDesc, nullptr); + + std::vector widgets; + this->cacheForEachIf(m_targetRow, m_targetCol, &CBoxAlgorithmP300TactileVisualization::cacheCollectChildWidgetCB, + &CBoxAlgorithmP300TactileVisualization::cacheCollectChildWidgetCB, &widgets, nullptr); + + // Merge the current target into the stimulation stream. It can be differentiated + // from a 'flash' spec because it IS between OVTK_StimulationId_RestStart and + // OVTK_StimulationId_RestStop stimulations in the P300 timeline. + { + m_stimuliQueue.push_back(m_targetRow + m_rowStimulationBase); + m_stimuliQueue.push_back(m_targetCol + m_columnStimulationBase); + } + + if (widgets.size() == 1) + { + if (GTK_IS_LABEL(widgets[0])) + { + std::string label; + label = gtk_label_get_text(m_target); + label += gtk_label_get_text(GTK_LABEL(widgets[0])); + gtk_label_set_text(m_target, label.c_str()); + } + else + { + this->getLogManager() << Kernel::LogLevel_Warning << "Expected label class widget... could not find a valid text to append\n"; + } + } + else + { + this->getLogManager() << Kernel::LogLevel_Warning << "Did not find a unique widget at row:" << size_t(m_targetRow) << " column:" << + size_t(m_targetCol) << "\n"; + } + + m_targetHistory.emplace_back(m_targetRow, m_targetCol); + m_lastTargetRow = m_targetRow; + m_lastTargetCol = m_targetCol; + m_targetRow = -1; + m_targetCol = -1; + } + } + } + + if (m_targetStimulationDecoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedEnd)) { } + + boxContext.markInputAsDeprecated(1, i); + } + } + + // --- Selection stimulations + + for (size_t k = 2; k < 4; ++k) + { + Kernel::IAlgorithmProxy* decoder = (k == 2 ? m_rowSelectionStimulationDecoder : m_columnSelectionStimulationDecoder); + Kernel::TParameterHandler selectionMemoryBuffer( + decoder->getInputParameter(OVP_GD_Algorithm_StimulationDecoder_InputParameterId_MemoryBufferToDecode)); + Kernel::TParameterHandler selectionStimulationSet( + decoder->getOutputParameter(OVP_GD_Algorithm_StimulationDecoder_OutputParameterId_StimulationSet)); + + for (size_t i = 0; i < boxContext.getInputChunkCount(k); ++i) + { + if (m_lastTime >= boxContext.getInputChunkStartTime(k, i)) + { + selectionMemoryBuffer = boxContext.getInputChunk(k, i); + decoder->process(); + + if (decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedHeader)) { } + + if (decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedBuffer)) + { + IStimulationSet* stimulationSet = selectionStimulationSet; + for (size_t j = 0; j < stimulationSet->getStimulationCount(); ++j) + { + uint64_t id = stimulationSet->getStimulationIdentifier(j); + bool selected = false; + if (id >= m_rowStimulationBase && id < m_rowStimulationBase + m_nRow) + { + this->getLogManager() << Kernel::LogLevel_Debug << "Received Selected Row " << id << "\n"; + m_selectedRow = int(id - m_rowStimulationBase); + selected = true; + } + if (id >= m_columnStimulationBase && id < m_columnStimulationBase + m_nRow) + { + this->getLogManager() << Kernel::LogLevel_Debug << "Received Selected Column " << id << "\n"; + m_selectedCol = int(id - m_columnStimulationBase); + selected = true; + } + if (id == OVTK_StimulationId_Label_00) + { + if (k == 2) { m_selectedRow = -2; } + if (k == 3) { m_selectedCol = -2; } + selected = true; + } + if (selected && m_selectedRow != -1 && m_selectedCol != -1) + { + if (m_selectedRow >= 0 && m_selectedCol >= 0) + { + this->getLogManager() << Kernel::LogLevel_Debug << "Displays Selected Cell\n"; + this->cacheForEachIf(m_selectedRow, m_selectedCol, &CBoxAlgorithmP300TactileVisualization::cacheChangeBackgroundCB, + &CBoxAlgorithmP300TactileVisualization::cacheChangeNullCB, &m_selectedBgColor, nullptr); + this->cacheForEachIf(m_selectedRow, m_selectedCol, &CBoxAlgorithmP300TactileVisualization::cacheChangeForegroundCB, + &CBoxAlgorithmP300TactileVisualization::cacheChangeNullCB, &m_selectedFgColor, nullptr); + this->cacheForEachIf(m_selectedRow, m_selectedCol, &CBoxAlgorithmP300TactileVisualization::cacheChangeFontCB, + &CBoxAlgorithmP300TactileVisualization::cacheChangeNullCB, m_selectedFontDesc, nullptr); + + std::vector widgets; + this->cacheForEachIf(m_selectedRow, m_selectedCol, &CBoxAlgorithmP300TactileVisualization::cacheCollectChildWidgetCB, + &CBoxAlgorithmP300TactileVisualization::cacheCollectChildWidgetCB, &widgets, nullptr); + + if (widgets.size() == 1) + { + if (GTK_IS_LABEL(widgets[0])) + { + std::string label; + label = gtk_label_get_text(GTK_LABEL(widgets[0])); + if (!m_targetHistory.empty()) + { + auto it = m_targetHistory.begin(); + bool correct = (it->first == m_selectedRow && it->second == m_selectedCol); + bool halfCorrect = (it->first == m_selectedRow || it->second == m_selectedCol); + m_targetHistory.pop_front(); + std::string tmp; + if (correct) { tmp = ""; } + else if (halfCorrect) { tmp = ""; } + else { tmp = ""; } + label = tmp.append(label).append(""); + } + label = std::string(gtk_label_get_label(m_result)).append(label); + gtk_label_set_markup(m_result, label.c_str()); + } + else + { + this->getLogManager() << Kernel::LogLevel_Warning << + "Expected label class widget... could not find a valid text to append\n"; + } + } + else + { + this->getLogManager() << Kernel::LogLevel_Warning << "Did not find a unique widget at row : " << size_t(m_selectedRow) << + " column : " << size_t(m_selectedCol) << "\n"; + } + } + else + { + this->getLogManager() << Kernel::LogLevel_Trace << "Selection Rejected !\n"; + std::string label; + label = gtk_label_get_text(m_result); + label += "*"; + gtk_label_set_text(m_result, label.c_str()); + } + + m_selectedRow = -1; + m_selectedCol = -1; + } + //Load new menuentries + uint64_t label_id = id - m_rowStimulationBase; + if(main) + { + set_labeltext(label_id); + main = false; + } + else if(label_id == 5) + { + set_labeltext(6); + main = true; + } + } + } + + if (decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedEnd)) { } + boxContext.markInputAsDeprecated(k, i); + } + } + } + + // After any possible rendering, we flush the accumulated stimuli. The default idle func is low priority, so it should be run after rendering by gtk. + if (m_idleFuncTag == 0) { m_idleFuncTag = g_idle_add(FlushCB, this); } + + return true; +} + +// _________________________________________________________________________________________________________________________________________________________ +// + +void CBoxAlgorithmP300TactileVisualization::cacheBuildFromTable(GtkTable* table) +{ + if (table) + { + const GdkColor white = InitGDKColor(65535, 65535, 65535, 65535); + + for (GList* list = table->children; list; list = list->next) + { + GtkTableChild* child = static_cast(list->data); + + for (size_t i = child->top_attach; i < child->bottom_attach; ++i) + { + for (size_t j = child->left_attach; j < child->right_attach; ++j) + { + widget_style_t& style = m_cache[i][j]; + style.widget = child->widget; + style.childWidget = gtk_bin_get_child(GTK_BIN(child->widget)); + style.bgColor = white; + style.fgColor = white; + style.fontDesc = nullptr; + } + } + } + } +} + +void CBoxAlgorithmP300TactileVisualization::cacheForEach(const cache_callback callback, void* data) +{ + for (auto i = m_cache.begin(); i != m_cache.end(); ++i) + { + for (auto j = i->second.begin(); j != i->second.end(); ++j) { (this->*callback)(j->second, data); } + } +} + +void CBoxAlgorithmP300TactileVisualization::cacheForEachIf(const int iLine, const int iColumn, const cache_callback ifCB, const cache_callback elseCB, + void* ifUserData, void* elseUserData) +{ + for (auto i = m_cache.begin(); i != m_cache.end(); ++i) + { + for (auto j = i->second.begin(); j != i->second.end(); ++j) + { + const bool line = (iLine != -1); + const bool column = (iColumn != -1); + bool inLine = false; + bool inCol = false; + bool first; + + if (line && size_t(iLine) == i->first) { inLine = true; } + if (column && size_t(iColumn) == j->first) { inCol = true; } + + if (line && column) { first = inLine && inCol; } + else { first = inLine || inCol; } + + if (first) { (this->*ifCB)(j->second, ifUserData); } + else { (this->*elseCB)(j->second, elseUserData); } + } + } +} + +void CBoxAlgorithmP300TactileVisualization::cacheChangeNullCB(widget_style_t& /*rWidgetStyle*/, void* /*data*/) { } + +void CBoxAlgorithmP300TactileVisualization::cacheChangeBackgroundCB(widget_style_t& style, void* data) +{ + GdkColor oColor = *static_cast(data); + if (memcmp(&style.bgColor, &oColor, sizeof(GdkColor)) != 0) + { + gtk_widget_modify_bg(style.widget, GTK_STATE_NORMAL, &oColor); + style.bgColor = oColor; + } +} + +void CBoxAlgorithmP300TactileVisualization::cacheChangeForegroundCB(widget_style_t& style, void* data) +{ + GdkColor oColor = *static_cast(data); + if (memcmp(&style.fgColor, &oColor, sizeof(GdkColor)) != 0) + { + gtk_widget_modify_fg(style.childWidget, GTK_STATE_NORMAL, &oColor); + style.fgColor = oColor; + } +} + +void CBoxAlgorithmP300TactileVisualization::cacheChangeFontCB(widget_style_t& style, void* data) +{ + auto* pFontDescription = static_cast(data); + if (style.fontDesc != pFontDescription) + { + gtk_widget_modify_font(style.childWidget, pFontDescription); + style.fontDesc = pFontDescription; + } +} + +void CBoxAlgorithmP300TactileVisualization::cacheCollectWidgetCB(widget_style_t& style, void* data) +{ + if (data) { (static_cast*>(data))->push_back(style.widget); } +} + +void CBoxAlgorithmP300TactileVisualization::cacheCollectChildWidgetCB(widget_style_t& style, void* data) +{ + if (data) { (static_cast*>(data))->push_back(style.childWidget); } +} + +// Note that we don't need concurrency control here as gtk callbacks run in the main thread +void CBoxAlgorithmP300TactileVisualization::flushQueue() +{ + for (const auto& stimulation : m_stimuliQueue) { m_stimulusSender->sendStimulation(stimulation); } + m_stimuliQueue.clear(); + + // This function will be automatically removed after completion, so set to 0 + m_idleFuncTag = 0; +} + +} // namespace Tactilebci +} // namespace Plugins +} // namespace OpenViBE diff --git a/src/TactileVisualization/ovpCBoxAlgorithmP300TactileVisualization.h b/src/TactileVisualization/ovpCBoxAlgorithmP300TactileVisualization.h new file mode 100644 index 0000000..dd5c266 --- /dev/null +++ b/src/TactileVisualization/ovpCBoxAlgorithmP300TactileVisualization.h @@ -0,0 +1,206 @@ +///------------------------------------------------------------------------------------------------- +/// +/// \file ovpCBoxAlgorithmP300TactileVisualization.h +/// \brief Classes of the Box P300 Tactile Visualization. !!This is a modification of the P300 Speller Visualization Box!! +/// \author Tobias Baumann (TH Nuernberg). +/// \version 1.0. +/// \date Mon Feb 04 12:43:53 2022. +/// \copyright GNU Affero General Public License v3.0. +/// +///------------------------------------------------------------------------------------------------- + +//includes +#pragma once + +#include "TactileMenu.h" +#include "../ovp_defines.h" +#include +#include +#include + +#include +#include +#include +#include "../utils.h" + +namespace TCPTagging { +class IStimulusSender; // fwd declare +} // namespace TCPTagging + +namespace OpenViBE { +namespace Plugins { +namespace Tactilebci { + +class CBoxAlgorithmP300TactileVisualization final : public Toolkit::TBoxAlgorithm +{ +public: + + void release() override { delete this; } + + bool initialize() override; + bool uninitialize() override; + bool processInput(const size_t index) override; + bool process() override; + + _IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm, OVP_ClassId_BoxAlgorithm_P300TactileVisualization) + + // Sends all accumulated stimuli to the TCP Tagging + void flushQueue(); + +private: + + Kernel::IAlgorithmProxy* m_sequenceStimulationDecoder = nullptr; + Kernel::IAlgorithmProxy* m_targetStimulationDecoder = nullptr; + Kernel::IAlgorithmProxy* m_targetFlaggingStimulationEncoder = nullptr; + Kernel::IAlgorithmProxy* m_rowSelectionStimulationDecoder = nullptr; + Kernel::IAlgorithmProxy* m_columnSelectionStimulationDecoder = nullptr; + Kernel::TParameterHandler m_sequenceMemoryBuffer; + Kernel::TParameterHandler m_targetMemoryBuffer; + Kernel::TParameterHandler m_targetFlaggingStimulationSet; + Kernel::TParameterHandler m_sequenceStimulationSet; + Kernel::TParameterHandler m_targetStimulationSet; + Kernel::TParameterHandler m_targetFlaggingMemoryBuffer; + uint64_t m_lastTime = 0; + + GtkBuilder* m_mainWidgetInterface = nullptr; + GtkBuilder* m_toolbarWidgetInterface = nullptr; + GtkWidget* m_mainWindow = nullptr; + GtkWidget* m_toolbarWidget = nullptr; + GtkTable* m_table = nullptr; + GtkLabel* m_result = nullptr; + GtkLabel* m_target = nullptr; + + //Deklaration TactileMenu Variablen + TactileMenu m_MainMenu; + TactileMenu m_CareMenu; + TactileMenu m_HelpMenu; + TactileMenu m_DevicesMenu; + TactileMenu m_FunctionsMenu; + TactileMenu* m_CurrentMenu; = nullptr; + + uint64_t m_nRow = 0; + uint64_t m_nCol = 0; + + int m_lastTargetRow = 0; + int m_lastTargetCol = 0; + int m_targetRow = 0; + int m_targetCol = 0; + int m_selectedRow = 0; + int m_selectedCol = 0; + + bool m_tableInitialized = false; + + using widget_style_t = struct + { + GtkWidget* widget; + GtkWidget* childWidget; + GdkColor bgColor; + GdkColor fgColor; + PangoFontDescription* fontDesc; + }; + + typedef void (CBoxAlgorithmP300TactileVisualization::*cache_callback)(widget_style_t& style, void* data); + + void cacheBuildFromTable(GtkTable* table); + void cacheForEach(cache_callback callback, void* data); + void cacheForEachIf(int iLine, int iColumn, cache_callback ifCB, cache_callback elseCB, void* ifUserData, void* elseUserData); + void cacheChangeNullCB(widget_style_t& style, void* data); + void cacheChangeBackgroundCB(widget_style_t& style, void* data); + void cacheChangeForegroundCB(widget_style_t& style, void* data); + void cacheChangeFontCB(widget_style_t& style, void* data); + void cacheCollectWidgetCB(widget_style_t& style, void* data); + void cacheCollectChildWidgetCB(widget_style_t& style, void* data); + + // @todo refactor to std::pair ? + std::map> m_cache; + std::list> m_targetHistory; + + // TCP Tagging + std::vector m_stimuliQueue; + guint m_idleFuncTag = 0; + TCPTagging::IStimulusSender* m_stimulusSender = nullptr; + + VisualizationToolkit::IVisualizationContext* m_visualizationCtx = nullptr; + +protected: + + CString m_interfaceFilename; + uint64_t m_rowStimulationBase = 0; + uint64_t m_columnStimulationBase = 0; + + GdkColor m_flashBgColor = InitGDKColor(0, 6554, 6554, 6554); + GdkColor m_flashFgColor = InitGDKColor(0, 65535, 65535, 65535); + uint64_t m_flashFontSize = 100; + PangoFontDescription* m_flashFontDesc = nullptr; + GdkColor m_noFlashBgColor = InitGDKColor(0, 0, 0, 0); + GdkColor m_noFlashFgColor = InitGDKColor(0, 32768, 32768, 32768); + uint64_t m_noFlashFontSize = 75; + PangoFontDescription* m_noFlashFontDesc = nullptr; + GdkColor m_targetBgColor = InitGDKColor(0, 6554, 26214, 6554); + GdkColor m_targetFgColor = InitGDKColor(0, 39321, 65535, 39321); + uint64_t m_targetFontSize = 100; + PangoFontDescription* m_targetFontDesc = nullptr; + GdkColor m_selectedBgColor = InitGDKColor(0, 45875, 13107, 13107); + GdkColor m_selectedFgColor = InitGDKColor(0, 19661, 6554, 6554); + uint64_t m_selectedFontSize = 100; + PangoFontDescription* m_selectedFontDesc = nullptr; +}; + +class CBoxAlgorithmP300TactileVisualizationDesc final : public IBoxAlgorithmDesc +{ +public: + + void release() override { } + + CString getName() const override { return CString("P300 Tactile Visualization"); } + CString getAuthorName() const override { return CString("Tobias Baumann"); } + CString getAuthorCompanyName() const override { return CString("TH-Nürnberg"); } + CString getShortDescription() const override { return CString("Visualizes the Menus for a Tactile P300 BCI"); } + CString getDetailedDescription() const override { return CString(""); } + CString getCategory() const override { return CString("TactileBCI"); } + CString getVersion() const override { return CString("1.0"); } + CString getStockItemName() const override { return CString("gtk-select-font"); } + + CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_P300TactileVisualization; } + IPluginObject* create() override { return new CBoxAlgorithmP300TactileVisualization; } + + bool hasFunctionality(const EPluginFunctionality functionality) const override { return functionality == EPluginFunctionality::Visualization; } + + bool getBoxPrototype(Kernel::IBoxProto& prototype) const override + { + prototype.addInput("Sequence stimulations", OV_TypeId_Stimulations); + prototype.addInput("Target stimulations", OV_TypeId_Stimulations); + prototype.addInput("Row selection stimulations", OV_TypeId_Stimulations); + prototype.addInput("Column selection stimulations", OV_TypeId_Stimulations); + + prototype.addOutput("Target / Non target flagging (deprecated)", OV_TypeId_Stimulations); + + prototype.addSetting("Interface filename", OV_TypeId_Filename, "${Path_Data}/plugins/simple-visualization/p300-speller.ui"); + prototype.addSetting("Row stimulation base", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01"); + prototype.addSetting("Column stimulation base", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_07"); + + prototype.addSetting("Flash background color", OV_TypeId_Color, "10,10,10"); + prototype.addSetting("Flash foreground color", OV_TypeId_Color, "100,100,100"); + prototype.addSetting("Flash font size", OV_TypeId_Integer, "100"); + + prototype.addSetting("No flash background color", OV_TypeId_Color, "0,0,0"); + prototype.addSetting("No flash foreground color", OV_TypeId_Color, "50,50,50"); + prototype.addSetting("No flash font size", OV_TypeId_Integer, "75"); + + prototype.addSetting("Target background color", OV_TypeId_Color, "10,40,10"); + prototype.addSetting("Target foreground color", OV_TypeId_Color, "60,100,60"); + prototype.addSetting("Target font size", OV_TypeId_Integer, "100"); + + prototype.addSetting("Selected background color", OV_TypeId_Color, "70,20,20"); + prototype.addSetting("Selected foreground color", OV_TypeId_Color, "30,10,10"); + prototype.addSetting("Selected font size", OV_TypeId_Integer, "100"); + + return true; + } + + _IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_P300TactileVisualizationDesc) +}; + +} // namespace Tactilebci +} // namespace Plugins +} // namespace OpenViBE