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