From 88280fa90b4b00dd2bbdc6788a5b03741280c5bd Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 3 Apr 2025 17:43:58 -0500 Subject: [PATCH 01/29] Refactor FFT settings variables across the GUI and Add GlobalFFTSettings Class --- OpenBCI_GUI/BandPowerEnums.pde | 121 +++++++++++++++++ OpenBCI_GUI/DataProcessing.pde | 127 +++++++++--------- OpenBCI_GUI/FFTEnums.pde | 228 ++++++++++++++++++++++++++++++++ OpenBCI_GUI/HeadPlotEnums.pde | 150 +++++++++++++++++++++ OpenBCI_GUI/OpenBCI_GUI.pde | 14 +- OpenBCI_GUI/SessionSettings.pde | 73 +++++----- OpenBCI_GUI/W_BandPower.pde | 179 +++++-------------------- OpenBCI_GUI/W_FFT.pde | 218 ++++++++++++++---------------- OpenBCI_GUI/W_HeadPlot.pde | 149 +++++++++------------ OpenBCI_GUI/W_Spectrogram.pde | 23 ++-- 10 files changed, 825 insertions(+), 457 deletions(-) create mode 100644 OpenBCI_GUI/BandPowerEnums.pde create mode 100644 OpenBCI_GUI/FFTEnums.pde create mode 100644 OpenBCI_GUI/HeadPlotEnums.pde diff --git a/OpenBCI_GUI/BandPowerEnums.pde b/OpenBCI_GUI/BandPowerEnums.pde new file mode 100644 index 000000000..cf6c322f7 --- /dev/null +++ b/OpenBCI_GUI/BandPowerEnums.pde @@ -0,0 +1,121 @@ + +public enum BPAutoClean implements IndexingInterface +{ + ON (0, "On"), + OFF (1, "Off"); + + private int index; + private String label; + private static BPAutoClean[] vals = values(); + + BPAutoClean(int _index, String _label) { + this.index = _index; + this.label = _label; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList(); + for (IndexingInterface val : vals) { + enumStrings.add(val.getString()); + } + return enumStrings; + } +} + +public enum BPAutoCleanThreshold implements IndexingInterface +{ + FORTY (0, 40f, "40 uV"), + FIFTY (1, 50f, "50 uV"), + SIXTY (2, 60f, "60 uV"), + SEVENTY (3, 70f, "70 uV"), + EIGHTY (4, 80f, "80 uV"), + NINETY (5, 90f, "90 uV"), + ONE_HUNDRED(6, 100f, "100 uV"); + + private int index; + private float value; + private String label; + private static BPAutoCleanThreshold[] vals = values(); + + BPAutoCleanThreshold(int _index, float _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public float getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList(); + for (IndexingInterface val : vals) { + enumStrings.add(val.getString()); + } + return enumStrings; + } +} + +public enum BPAutoCleanTimer implements IndexingInterface +{ + HALF_SECOND (0, 500, ".5 sec"), + ONE_SECOND (1, 1000, "1 sec"), + THREE_SECONDS (2, 2000, "3 sec"), + FIVE_SECONDS (3, 5000, "5 sec"), + TEN_SECONDS (4, 10000, "10 sec"), + TWENTY_SECONDS (5, 20000, "20 sec"), + THIRTY_SECONDS(6, 30000, "30 sec"); + + private int index; + private float value; + private String label; + private static BPAutoCleanTimer[] vals = values(); + + BPAutoCleanTimer(int _index, float _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public float getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList(); + for (IndexingInterface val : vals) { + enumStrings.add(val.getString()); + } + return enumStrings; + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/DataProcessing.pde b/OpenBCI_GUI/DataProcessing.pde index d7045876f..2d113528e 100644 --- a/OpenBCI_GUI/DataProcessing.pde +++ b/OpenBCI_GUI/DataProcessing.pde @@ -28,24 +28,24 @@ void processNewData() { } //update the data buffers - for (int Ichan=0; Ichan < channelCount; Ichan++) { + for (int channel=0; channel < channelCount; channel++) { for(int i = 0; i < getCurrentBoardBufferSize(); i++) { - dataProcessingRawBuffer[Ichan][i] = (float)currentData.get(i)[exgChannels[Ichan]]; + dataProcessingRawBuffer[channel][i] = (float)currentData.get(i)[exgChannels[channel]]; } - dataProcessingFilteredBuffer[Ichan] = dataProcessingRawBuffer[Ichan].clone(); + dataProcessingFilteredBuffer[channel] = dataProcessingRawBuffer[channel].clone(); } //apply additional processing for the time-domain montage plot (ie, filtering) dataProcessing.process(dataProcessingFilteredBuffer, fftBuff); //look to see if the latest data is railed so that we can notify the user on the GUI - for (int Ichan=0; Ichan < globalChannelCount; Ichan++) is_railed[Ichan].update(dataProcessingRawBuffer[Ichan], Ichan); + for (int channel=0; channel < globalChannelCount; channel++) is_railed[channel].update(dataProcessingRawBuffer[channel], channel); //compute the electrode impedance. Do it in a very simple way [rms to amplitude, then uVolt to Volt, then Volt/Amp to Ohm] - for (int Ichan=0; Ichan < globalChannelCount; Ichan++) { + for (int channel=0; channel < globalChannelCount; channel++) { // Calculate the impedance - float impedance = (sqrt(2.0)*dataProcessing.data_std_uV[Ichan]*1.0e-6) / BoardCytonConstants.leadOffDrive_amps; + float impedance = (sqrt(2.0)*dataProcessing.data_std_uV[channel]*1.0e-6) / BoardCytonConstants.leadOffDrive_amps; // Subtract the 2.2kOhm resistor impedance -= BoardCytonConstants.series_resistor_ohms; // Verify the impedance is not less than 0 @@ -54,25 +54,24 @@ void processNewData() { impedance = 0; } // Store to the global variable - data_elec_imp_ohm[Ichan] = impedance; + data_elec_imp_ohm[channel] = impedance; } } -void initializeFFTObjects(ddf.minim.analysis.FFT[] fftBuff, float[][] dataProcessingRawBuffer, int Nfft, float fs_Hz) { - +void initializeFFTObjects(ddf.minim.analysis.FFT[] fftBuff, float[][] dataProcessingRawBuffer, int fftPointCount, float fs_Hz) { float[] fooData; - for (int Ichan=0; Ichan < globalChannelCount; Ichan++) { + for (int channel=0; channel < globalChannelCount; channel++) { //make the FFT objects...Following "SoundSpectrum" example that came with the Minim library - fftBuff[Ichan].window(ddf.minim.analysis.FFT.HAMMING); + fftBuff[channel].window(ddf.minim.analysis.FFT.HAMMING); //do the FFT on the initial data - if (isFFTFiltered == true) { - fooData = dataProcessingFilteredBuffer[Ichan]; //use the filtered data for the FFT + if (globalFFTSettings.getDataIsFiltered()) { + fooData = dataProcessingFilteredBuffer[channel]; //use the filtered data for the FFT } else { - fooData = dataProcessingRawBuffer[Ichan]; //use the raw data for the FFT + fooData = dataProcessingRawBuffer[channel]; //use the raw data for the FFT } - fooData = Arrays.copyOfRange(fooData, fooData.length-Nfft, fooData.length); - fftBuff[Ichan].forward(fooData); //compute FFT on this channel of data + fooData = Arrays.copyOfRange(fooData, fooData.length-fftPointCount, fooData.length); + fftBuff[channel].forward(fooData); //compute FFT on this channel of data } } @@ -112,37 +111,37 @@ class DataProcessing { } //Process data on a channel-by-channel basis - private synchronized void processChannel(int Ichan, float[][] data_forDisplay_uV, float[] prevFFTdata) { - int Nfft = getNfftSafe(); + private synchronized void processChannel(int channel, float[][] data_forDisplay_uV, float[] prevFFTdata) { + int fftPointCount = getNumFFTPoints(); double foo; // Filter the data in the time domain // TODO: Use double arrays here and convert to float only to plot data. // ^^^ This might not feasible or meaningful performance improvement. I looked into it a while ago and it seems we need floats for the FFT library also. -RW 2022) try { - double[] tempArray = floatToDoubleArray(data_forDisplay_uV[Ichan]); + double[] tempArray = floatToDoubleArray(data_forDisplay_uV[channel]); //Apply BandStop filter if the filter should be active on this channel - if (filterSettings.values.bandStopFilterActive[Ichan].isActive()) { + if (filterSettings.values.bandStopFilterActive[channel].isActive()) { DataFilter.perform_bandstop( tempArray, currentBoard.getSampleRate(), - filterSettings.values.bandStopStartFreq[Ichan], - filterSettings.values.bandStopStopFreq[Ichan], - filterSettings.values.bandStopFilterOrder[Ichan].getValue(), - filterSettings.values.bandStopFilterType[Ichan].getValue(), + filterSettings.values.bandStopStartFreq[channel], + filterSettings.values.bandStopStopFreq[channel], + filterSettings.values.bandStopFilterOrder[channel].getValue(), + filterSettings.values.bandStopFilterType[channel].getValue(), 1.0); } //Apply BandPass filter if the filter should be active on this channel - if (filterSettings.values.bandPassFilterActive[Ichan].isActive()) { + if (filterSettings.values.bandPassFilterActive[channel].isActive()) { DataFilter.perform_bandpass( tempArray, currentBoard.getSampleRate(), - filterSettings.values.bandPassStartFreq[Ichan], - filterSettings.values.bandPassStopFreq[Ichan], - filterSettings.values.bandPassFilterOrder[Ichan].getValue(), - filterSettings.values.bandPassFilterType[Ichan].getValue(), + filterSettings.values.bandPassStartFreq[channel], + filterSettings.values.bandPassStopFreq[channel], + filterSettings.values.bandPassFilterOrder[channel].getValue(), + filterSettings.values.bandPassFilterType[channel].getValue(), 1.0); } @@ -190,66 +189,68 @@ class DataProcessing { break; } - doubleToFloatArray(tempArray, data_forDisplay_uV[Ichan]); + doubleToFloatArray(tempArray, data_forDisplay_uV[channel]); } catch (BrainFlowError e) { e.printStackTrace(); } //compute the standard deviation of the filtered signal...this is for the head plot - float[] fooData_filt = dataProcessingFilteredBuffer[Ichan]; //use the filtered data + float[] fooData_filt = dataProcessingFilteredBuffer[channel]; //use the filtered data fooData_filt = Arrays.copyOfRange(fooData_filt, fooData_filt.length-((int)fs_Hz), fooData_filt.length); //just grab the most recent second of data - data_std_uV[Ichan]=std(fooData_filt); //compute the standard deviation for the whole array "fooData_filt" + data_std_uV[channel] = std(fooData_filt); //compute the standard deviation for the whole array "fooData_filt" //copy the previous FFT data...enables us to apply some smoothing to the FFT data - for (int I=0; I < fftBuff[Ichan].specSize(); I++) { - prevFFTdata[I] = fftBuff[Ichan].getBand(I); //copy the old spectrum values + for (int I=0; I < fftBuff[channel].specSize(); I++) { + prevFFTdata[I] = fftBuff[channel].getBand(I); //copy the old spectrum values } //prepare the data for the new FFT float[] fooData; - if (isFFTFiltered == true) { - fooData = dataProcessingFilteredBuffer[Ichan]; //use the filtered data for the FFT + if (globalFFTSettings.getDataIsFiltered()) { + fooData = dataProcessingFilteredBuffer[channel]; //use the filtered data for the FFT } else { - fooData = dataProcessingRawBuffer[Ichan]; //use the raw data for the FFT + fooData = dataProcessingRawBuffer[channel]; //use the raw data for the FFT } - fooData = Arrays.copyOfRange(fooData, fooData.length-Nfft, fooData.length); //trim to grab just the most recent block of data + fooData = Arrays.copyOfRange(fooData, fooData.length-fftPointCount, fooData.length); //trim to grab just the most recent block of data float meanData = mean(fooData); //compute the mean for (int I=0; I < fooData.length; I++) fooData[I] -= meanData; //remove the mean (for a better looking FFT //compute the FFT - fftBuff[Ichan].forward(fooData); //compute FFT on this channel of data + fftBuff[channel].forward(fooData); //compute FFT on this channel of data // FFT ref: https://www.mathworks.com/help/matlab/ref/fft.html // first calculate double-sided FFT amplitude spectrum - for (int I=0; I <= Nfft/2; I++) { - fftBuff[Ichan].setBand(I, (float)(fftBuff[Ichan].getBand(I) / Nfft)); + for (int I=0; I <= fftPointCount/2; I++) { + fftBuff[channel].setBand(I, (float)(fftBuff[channel].getBand(I) / fftPointCount)); } // then convert into single-sided FFT spectrum: DC & Nyquist (i=0 & i=N/2) remain the same, others multiply by two. - for (int I=1; I < Nfft/2; I++) { - fftBuff[Ichan].setBand(I, (float)(fftBuff[Ichan].getBand(I) * 2)); + for (int I=1; I < fftPointCount/2; I++) { + fftBuff[channel].setBand(I, (float)(fftBuff[channel].getBand(I) * 2)); } //average the FFT with previous FFT data so that it makes it smoother in time double min_val = 0.01d; - for (int I=0; I < fftBuff[Ichan].specSize(); I++) { //loop over each fft bin + float smoothingFactor = globalFFTSettings.getSmoothingFactor().getValue(); + for (int I=0; I < fftBuff[channel].specSize(); I++) { //loop over each fft bin if (prevFFTdata[I] < min_val) prevFFTdata[I] = (float)min_val; //make sure we're not too small for the log calls - foo = fftBuff[Ichan].getBand(I); + foo = fftBuff[channel].getBand(I); if (foo < min_val) foo = min_val; //make sure this value isn't too small if (true) { //smooth in dB power space - foo = (1.0d-smoothFac[smoothFac_ind]) * java.lang.Math.log(java.lang.Math.pow(foo, 2)); - foo += smoothFac[smoothFac_ind] * java.lang.Math.log(java.lang.Math.pow((double)prevFFTdata[I], 2)); + foo = (1.0d - smoothingFactor) * java.lang.Math.log(java.lang.Math.pow(foo, 2)); + foo += smoothingFactor * java.lang.Math.log(java.lang.Math.pow((double)prevFFTdata[I], 2)); foo = java.lang.Math.sqrt(java.lang.Math.exp(foo)); //average in dB space } else { + //LEGACY CODE -- NOT USED //smooth (average) in linear power space - foo = (1.0d-smoothFac[smoothFac_ind]) * java.lang.Math.pow(foo, 2); - foo+= smoothFac[smoothFac_ind] * java.lang.Math.pow((double)prevFFTdata[I], 2); + foo = (1.0d - smoothingFactor) * java.lang.Math.pow(foo, 2); + foo+= smoothingFactor * java.lang.Math.pow((double)prevFFTdata[I], 2); // take sqrt to be back into uV_rtHz foo = java.lang.Math.sqrt(foo); } - fftBuff[Ichan].setBand(I, (float)foo); //put the smoothed data back into the fftBuff data holder for use by everyone else - // fftBuff[Ichan].setBand(I, 1.0f); // test + fftBuff[channel].setBand(I, (float)foo); //put the smoothed data back into the fftBuff data holder for use by everyone else + // fftBuff[channel].setBand(I, 1.0f); // test } //end loop over FFT bins // calculate single-sided psd by single-sided FFT amplitude spectrum @@ -260,22 +261,22 @@ class DataProcessing { for (int i = 0; i < processing_band_low_Hz.length; i++) { float sum = 0; // int binNum = 0; - for (int Ibin = 0; Ibin <= Nfft/2; Ibin ++) { // loop over FFT bins - float FFT_freq_Hz = fftBuff[Ichan].indexToFreq(Ibin); // center frequency of this bin + for (int Ibin = 0; Ibin <= fftPointCount/2; Ibin ++) { // loop over FFT bins + float FFT_freq_Hz = fftBuff[channel].indexToFreq(Ibin); // center frequency of this bin float psdx = 0; // if the frequency matches a band if (FFT_freq_Hz >= processing_band_low_Hz[i] && FFT_freq_Hz < processing_band_high_Hz[i]) { - if (Ibin != 0 && Ibin != Nfft/2) { - psdx = fftBuff[Ichan].getBand(Ibin) * fftBuff[Ichan].getBand(Ibin) * Nfft/currentBoard.getSampleRate() / 4; + if (Ibin != 0 && Ibin != fftPointCount/2) { + psdx = fftBuff[channel].getBand(Ibin) * fftBuff[channel].getBand(Ibin) * fftPointCount/currentBoard.getSampleRate() / 4; } else { - psdx = fftBuff[Ichan].getBand(Ibin) * fftBuff[Ichan].getBand(Ibin) * Nfft/currentBoard.getSampleRate(); + psdx = fftBuff[channel].getBand(Ibin) * fftBuff[channel].getBand(Ibin) * fftPointCount/currentBoard.getSampleRate(); } sum += psdx; // binNum ++; } } - avgPowerInBins[Ichan][i] = sum; // total power in a band + avgPowerInBins[channel][i] = sum; // total power in a band // println(i, binNum, sum); } } @@ -284,8 +285,8 @@ class DataProcessing { float prevFFTdata[] = new float[fftBuff[0].specSize()]; - for (int Ichan=0; Ichan < globalChannelCount; Ichan++) { - processChannel(Ichan, data_forDisplay_uV, prevFFTdata); + for (int channel=0; channel < globalChannelCount; channel++) { + processChannel(channel, data_forDisplay_uV, prevFFTdata); } //end the loop over channels. for (int i = 0; i < processing_band_low_Hz.length; i++) { @@ -304,14 +305,14 @@ class DataProcessing { float[] refData_uV = dataProcessingFilteredBuffer[refChanInd]; //use the filtered data refData_uV = Arrays.copyOfRange(refData_uV, refData_uV.length-((int)fs_Hz), refData_uV.length); //just grab the most recent second of data // Compute polarity of each channel - for (int Ichan=0; Ichan < globalChannelCount; Ichan++) { - float[] fooData_filt = dataProcessingFilteredBuffer[Ichan]; //use the filtered data + for (int channel=0; channel < globalChannelCount; channel++) { + float[] fooData_filt = dataProcessingFilteredBuffer[channel]; //use the filtered data fooData_filt = Arrays.copyOfRange(fooData_filt, fooData_filt.length-((int)fs_Hz), fooData_filt.length); //just grab the most recent second of data float dotProd = calcDotProduct(fooData_filt, refData_uV); if (dotProd >= 0.0f) { - polarity[Ichan]=1.0; + polarity[channel]=1.0; } else { - polarity[Ichan]=-1.0; + polarity[channel]=-1.0; } } diff --git a/OpenBCI_GUI/FFTEnums.pde b/OpenBCI_GUI/FFTEnums.pde new file mode 100644 index 000000000..3602b98bf --- /dev/null +++ b/OpenBCI_GUI/FFTEnums.pde @@ -0,0 +1,228 @@ + +public class GlobalFFTSettings { + public FFTSmoothingFactor smoothingFactor = FFTSmoothingFactor.SMOOTH_90; + public FFTFilteredEnum dataIsFiltered = FFTFilteredEnum.FILTERED; + + GlobalFFTSettings() { + // Constructor + } + + public void setSmoothingFactor(FFTSmoothingFactor factor) { + this.smoothingFactor = factor; + } + + public FFTSmoothingFactor getSmoothingFactor() { + return smoothingFactor; + } + + public void setFilteredEnum(FFTFilteredEnum filteredEnum) { + this.dataIsFiltered = filteredEnum; + } + + public FFTFilteredEnum getFilteredEnum() { + return dataIsFiltered; + } + + public boolean getDataIsFiltered() { + return dataIsFiltered == FFTFilteredEnum.FILTERED; + } +} + + +// Used by FFT Widget, Band Power Widget, and Head Plot Widget +public enum FFTSmoothingFactor implements IndexingInterface { + NONE (0, 0.0f, "O.O"), + SMOOTH_50 (1, 0.5f, "0.5"), + SMOOTH_75 (2, 0.75f, "0.75"), + SMOOTH_90 (3, 0.9f, "0.9"), + SMOOTH_95 (4, 0.95f, "0.95"), + SMOOTH_98 (5, 0.98f, "0.98"), + SMOOTH_99 (6, 0.99f, "0.99"), + SMOOTH_999 (7, 0.999f, "0.999"); + + private final int index; + private final float value; + private final String label; + private static final FFTSmoothingFactor[] values = values(); + + FFTSmoothingFactor(int index, float value, String label) { + this.index = index; + this.value = value; + this.label = label; + } + + public float getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} + +// Used by FFT Widget and Band Power Widget +public enum FFTFilteredEnum implements IndexingInterface { + FILTERED (0, "Filtered"), + UNFILTERED (1, "Unfiltered"); + + private final int index; + private final String label; + private static final FFTFilteredEnum[] values = values(); + + FFTFilteredEnum(int index, String label) { + this.index = index; + this.label = label; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} + +public enum FFTMaxFrequency implements IndexingInterface { + MAX_20 (0, 20, "20 Hz"), + MAX_40 (1, 40, "40 Hz"), + MAX_60 (2, 60, "60 Hz"), + MAX_100 (3, 100, "100 Hz"), + MAX_120 (4, 120, "120 Hz"), + MAX_250 (5, 250, "250 Hz"), + MAX_500 (6, 500, "500 Hz"), + MAX_800 (7, 800, "800 Hz"); + + private final int index; + private final int value; + private final String label; + private static final FFTMaxFrequency[] values = values(); + + FFTMaxFrequency(int index, int value, String label) { + this.index = index; + this.value = value; + this.label = label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } + + public int getHighestFrequency() { + return MAX_800.getValue(); + } +} + +public enum FFTVerticalScale implements IndexingInterface { + SCALE_10 (0, 10, "10 uV"), + SCALE_50 (1, 50, "50 uV"), + SCALE_100 (2, 100, "100 uV"), + SCALE_1000 (3, 1000, "1000 uV"); + + private final int index; + private final int value; + private final String label; + private static final FFTVerticalScale[] values = values(); + + FFTVerticalScale(int index, int value, String label) { + this.index = index; + this.value = value; + this.label = label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} + +public enum FFTLogLin implements IndexingInterface { + LOG (0, "Log"), + LIN (1, "Linear"); + + private final int index; + private final String label; + private static final FFTLogLin[] values = values(); + + FFTLogLin(int index, String label) { + this.index = index; + this.label = label; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/HeadPlotEnums.pde b/OpenBCI_GUI/HeadPlotEnums.pde new file mode 100644 index 000000000..8e00cf0f0 --- /dev/null +++ b/OpenBCI_GUI/HeadPlotEnums.pde @@ -0,0 +1,150 @@ + +public enum HeadPlotIntensity implements IndexingInterface { + INTENSITY_0_02 (0, .02f, "0.02x"), + INTENSITY_0_2 (1, .2f, "0.2x"), + INTENSITY_0_5 (2, .5f, "0.5x"), + INTENSITY_1 (3, 1.0f, "1x"), + INTENSITY_2 (4, 2.0f, "2x"), + INTENSITY_4 (5, 4.0f, "4x"); + + private final int index; + private final float value; + private final String label; + private static final HeadPlotIntensity[] values = values(); + + HeadPlotIntensity(int index, float value, String label) { + this.index = index; + this.value = value; + this.label = label; + } + + public float getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} + +public enum HeadPlotPolarity implements IndexingInterface { + PLUS_AND_MINUS (0, "+/-"), + PLUS (1, "+"); + + private final int index; + private final String label; + private static final HeadPlotPolarity[] values = values(); + + HeadPlotPolarity(int index, String label) { + this.index = index; + this.label = label; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} + +public enum HeadPlotContours implements IndexingInterface { + ON (0, "ON"), + OFF (1, "OFF"); + + private final int index; + private final String label; + private static final HeadPlotContours[] values = values(); + + HeadPlotContours(int index, String label) { + this.index = index; + this.label = label; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} + +public enum HeadPlotSmoothing implements IndexingInterface { + NONE (0, 0.0f, "O.O"), + SMOOTH_50 (1, 0.5f, "0.5"), + SMOOTH_75 (2, 0.75f, "0.75"), + SMOOTH_90 (3, 0.9f, "0.9"), + SMOOTH_95 (4, 0.95f, "0.95"), + SMOOTH_98 (5, 0.98f, "0.98"), + SMOOTH_99 (6, 0.99f, "0.99"), + SMOOTH_999 (7, 0.999f, "0.999"); + + private final int index; + private final float value; + private final String label; + private static final HeadPlotSmoothing[] values = values(); + + HeadPlotSmoothing(int index, float value, String label) { + this.index = index; + this.value = value; + this.label = label; + } + + public float getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde index f2ea0f895..d419af2b9 100644 --- a/OpenBCI_GUI/OpenBCI_GUI.pde +++ b/OpenBCI_GUI/OpenBCI_GUI.pde @@ -305,7 +305,7 @@ final int navBarHeight = 32; TopNav topNav; ddf.minim.analysis.FFT[] fftBuff = new ddf.minim.analysis.FFT[globalChannelCount]; //from the minim library -boolean isFFTFiltered = true; //yes by default ... this is used in dataProcessing.pde to determine which uV array feeds the FFT calculation +GlobalFFTSettings globalFFTSettings; StringBuilder globalScreenResolution; StringBuilder globalScreenDPI; @@ -803,7 +803,7 @@ public int getDownsampledBufferSize() { * @description Get the correct points of FFT based on sampling rate * @returns `int` - Points of FFT. 125Hz, 200Hz, 250Hz -> 256points. 1000Hz -> 1024points. 1600Hz -> 2048 points. */ -int getNfftSafe() { +int getNumFFTPoints() { int sampleRate = currentBoard.getSampleRate(); switch (sampleRate) { case 500: @@ -837,15 +837,17 @@ void initCoreDataObjects() { void initFFTObjectsAndBuffer() { //initialize the FFT objects - for (int Ichan=0; Ichan < globalChannelCount; Ichan++) { - // verbosePrint("Init FFT Buff – " + Ichan); - fftBuff[Ichan] = new ddf.minim.analysis.FFT(getNfftSafe(), currentBoard.getSampleRate()); + for (int channel = 0; channel < globalChannelCount; channel++) { + // verbosePrint("Init FFT Buff – " + channel); + fftBuff[channel] = new ddf.minim.analysis.FFT(getNumFFTPoints(), currentBoard.getSampleRate()); } //make the FFT objects + globalFFTSettings = new GlobalFFTSettings(); + //Attempt initialization. If error, print to console and exit function. //Fixes GUI crash when trying to load outdated recordings try { - initializeFFTObjects(fftBuff, dataProcessingRawBuffer, getNfftSafe(), currentBoard.getSampleRate()); + initializeFFTObjects(fftBuff, dataProcessingRawBuffer, getNumFFTPoints(), currentBoard.getSampleRate()); } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); outputError("Playback file load error. Try using a more recent recording."); diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde index 446370204..fe701fd21 100644 --- a/OpenBCI_GUI/SessionSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -36,7 +36,7 @@ ///////////////////////////////// class SessionSettings { //Current version to save to JSON - String settingsVersion = "3.0.0"; + String settingsVersion = "4.0.0"; //for screen resizing boolean screenHasBeenResized = false; float timeOfLastScreenResize = 0; @@ -61,26 +61,13 @@ class SessionSettings { //Accelerometer settings int accVertScaleSave; int accHorizScaleSave; - //FFT plot settings, - int fftMaxFrqSave; - int fftMaxuVSave; - int fftLogLinSave; - int fftSmoothingSave; - int fftFilterSave; //Analog Read settings int arVertScaleSave; int arHorizScaleSave; - //Headplot settings - int hpIntensitySave; - int hpPolaritySave; - int hpContoursSave; - int hpSmoothingSave; - //Used to check if a playback file has data - int minNumRowsPlaybackFile = int(currentBoard.getSampleRate()); //Spectrogram Widget settings + //FIX ME REMOVE THIS int spectMaxFrqSave; int spectSampleRateSave; - int spectLogLinSave; //default configuration settings file location and file name variables private String sessionPath = ""; @@ -103,13 +90,6 @@ class SessionSettings { "SynthSixteenDefaultSettings.json" }; - //Used to set text in dropdown menus when loading FFT settings - String[] fftMaxFrqArray = {"20 Hz", "40 Hz", "60 Hz", "100 Hz", "120 Hz", "250 Hz", "500 Hz", "800 Hz"}; - String[] fftVertScaleArray = {"10 uV", "50 uV", "100 uV", "1000 uV"}; - String[] fftLogLinArray = {"Log", "Linear"}; //share this with spectrogram also - String[] fftSmoothingArray = {"0.0", "0.5", "0.75", "0.9", "0.95", "0.98", "0.99", "0.999"}; - String[] fftFilterArray = {"Filtered", "Unfilt."}; - //Used to set text in dropdown menus when loading Accelerometer settings String[] accVertScaleArray = {"Auto","1 g", "2 g"}; String[] accHorizScaleArray = {"Sync", "1 sec", "3 sec", "5 sec", "10 sec", "20 sec"}; @@ -118,12 +98,6 @@ class SessionSettings { String[] arVertScaleArray = {"Auto", "50", "100", "200", "400", "1000", "10000"}; String[] arHorizScaleArray = {"Sync", "1 sec", "3 sec", "5 sec", "10 sec", "20 sec"}; - //Used to set text in dropdown menus when loading Head Plot settings - String[] hpIntensityArray = {"4x", "2x", "1x", "0.5x", "0.2x", "0.02x"}; - String[] hpPolarityArray = {"+/-", " + "}; - String[] hpContoursArray = {"ON", "OFF"}; - String[] hpSmoothingArray = {"0.0", "0.5", "0.75", "0.9", "0.95", "0.98"}; - //Used to set text in dropdown menus when loading Spectrogram Setings String[] spectMaxFrqArray = {"20 Hz", "40 Hz", "60 Hz", "100 Hz", "120 Hz", "250 Hz"}; String[] spectSampleRateArray = {"30 Min.", "6 Min.", "3 Min.", "1.5 Min.", "1 Min."}; @@ -344,6 +318,8 @@ class SessionSettings { ///////////////////////////////////////////////Setup new JSON object to save FFT settings JSONObject saveFFTSettings = new JSONObject(); + //FIX ME + /* //Save FFT_Max Freq Setting. The max frq variable is updated every time the user selects a dropdown in the FFT widget saveFFTSettings.setInt("FFT_Max Freq", fftMaxFrqSave); //Save FFT_Max uV Setting. The max uV variable is updated also when user selects dropdown in the FFT widget @@ -356,6 +332,7 @@ class SessionSettings { if (isFFTFiltered == true) fftFilterSave = 0; if (isFFTFiltered == false) fftFilterSave = 1; saveFFTSettings.setInt("FFT_Filter", fftFilterSave); + */ //Set the FFT JSON Object saveSettingsJSONData.setJSONObject(kJSONKeyFFT, saveFFTSettings); //next object will be set to sessionSettingsChannelCount+3, etc. @@ -376,6 +353,8 @@ class SessionSettings { if (w_headPlot != null) { JSONObject saveHeadplotSettings = new JSONObject(); + //FIX ME + /* //Save Headplot Intesity saveHeadplotSettings.setInt("HP_intensity", hpIntensitySave); //Save Headplot Polarity @@ -385,12 +364,15 @@ class SessionSettings { //Save Headplot Smoothing Setting saveHeadplotSettings.setInt("HP_smoothing", hpSmoothingSave); //Set the Headplot JSON Object + */ saveSettingsJSONData.setJSONObject(kJSONKeyHeadplot, saveHeadplotSettings); } ///////////////////////////////////////////////Setup new JSON object to save Band Power settings JSONObject saveBPSettings = new JSONObject(); - + + /* + //FIX ME //Save data from the Active channel checkBoxes JSONArray saveActiveChanBP = new JSONArray(); int numActiveBPChan = w_bandPower.bpChanSelect.getActiveChannels().size(); @@ -402,6 +384,7 @@ class SessionSettings { saveBPSettings.setInt("bpAutoClean", w_bandPower.getAutoClean().getIndex()); saveBPSettings.setInt("bpAutoCleanThreshold", w_bandPower.getAutoCleanThreshold().getIndex()); saveBPSettings.setInt("bpAutoCleanTimer", w_bandPower.getAutoCleanTimer().getIndex()); + */ saveSettingsJSONData.setJSONObject(kJSONKeyBandPower, saveBPSettings); ///////////////////////////////////////////////Setup new JSON object to save Spectrogram settings @@ -423,9 +406,12 @@ class SessionSettings { } saveSpectrogramSettings.setJSONArray("activeChannelsBot", saveActiveChanSpectBot); //Save Spectrogram_Max Freq Setting. The max frq variable is updated every time the user selects a dropdown in the spectrogram widget + //FIX ME + /* saveSpectrogramSettings.setInt("Spectrogram_Max Freq", spectMaxFrqSave); saveSpectrogramSettings.setInt("Spectrogram_Sample Rate", spectSampleRateSave); saveSpectrogramSettings.setInt("Spectrogram_LogLin", spectLogLinSave); + */ saveSettingsJSONData.setJSONObject(kJSONKeySpectrogram, saveSpectrogramSettings); ///////////////////////////////////////////////Setup new JSON object to save EMG Settings @@ -540,12 +526,15 @@ class SessionSettings { Boolean loadDataSmoothingSetting = (currentBoard instanceof SmoothingCapableBoard) ? loadGlobalSettings.getBoolean("Data Smoothing") : null; //get the FFT settings + //FIX ME + /* JSONObject loadFFTSettings = loadSettingsJSONData.getJSONObject(kJSONKeyFFT); fftMaxFrqLoad = loadFFTSettings.getInt("FFT_Max Freq"); fftMaxuVLoad = loadFFTSettings.getInt("FFT_Max uV"); fftLogLinLoad = loadFFTSettings.getInt("FFT_LogLin"); fftSmoothingLoad = loadFFTSettings.getInt("FFT_Smoothing"); fftFilterLoad = loadFFTSettings.getInt("FFT_Filter"); + */ //get the Accelerometer settings if (w_accelerometer != null) { @@ -559,14 +548,19 @@ class SessionSettings { //get the Headplot settings if (w_headPlot != null) { + //FIX ME + /* JSONObject loadHeadplotSettings = loadSettingsJSONData.getJSONObject(kJSONKeyHeadplot); hpIntensityLoad = loadHeadplotSettings.getInt("HP_intensity"); hpPolarityLoad = loadHeadplotSettings.getInt("HP_polarity"); hpContoursLoad = loadHeadplotSettings.getInt("HP_contours"); hpSmoothingLoad = loadHeadplotSettings.getInt("HP_smoothing"); + */ } //Get Band Power widget settings + //FIX ME + /* loadBPActiveChans.clear(); JSONObject loadBPSettings = loadSettingsJSONData.getJSONObject(kJSONKeyBandPower); JSONArray loadBPChan = loadBPSettings.getJSONArray("activeChannels"); @@ -577,6 +571,7 @@ class SessionSettings { loadBPAutoCleanThreshold = loadBPSettings.getInt("bpAutoCleanThreshold"); loadBPAutoCleanTimer = loadBPSettings.getInt("bpAutoCleanTimer"); //println("Settings: band power active chans loaded = " + loadBPActiveChans ); + */ try { //Get Spectrogram widget settings @@ -681,6 +676,8 @@ class SessionSettings { loadApplyTimeSeriesSettings(); if (w_headPlot != null) { + //FIX ME + /* //Force headplot to redraw if it is active int hpWidgetNumber; if (eegDataSource == DATASOURCE_GANGLION) { @@ -692,6 +689,7 @@ class SessionSettings { w_headPlot.headPlot.setPositionSize(w_headPlot.headPlot.hp_x, w_headPlot.headPlot.hp_y, w_headPlot.headPlot.hp_w, w_headPlot.headPlot.hp_h, w_headPlot.headPlot.hp_win_x, w_headPlot.headPlot.hp_win_y); println("Headplot is active: Redrawing"); } + */ } } //end of loadGUISettings ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -702,6 +700,8 @@ class SessionSettings { ////////Apply Time Series dropdown settings in loadApplyTimeSeriesSettings() instead of here ////////Apply FFT settings + //FIX ME + /* MaxFreq(fftMaxFrqLoad); //This changes the back-end w_fft.cp5_widget.getController("MaxFreq").getCaptionLabel().setText(fftMaxFrqArray[fftMaxFrqLoad]); //This changes front-end... etc. @@ -716,6 +716,7 @@ class SessionSettings { UnfiltFilt(fftFilterLoad); w_fft.cp5_widget.getController("UnfiltFilt").getCaptionLabel().setText(fftFilterArray[fftFilterLoad]); + */ ////////Apply Accelerometer settings; if (w_accelerometer != null) { @@ -737,6 +738,8 @@ class SessionSettings { } ////////////////////////////Apply Headplot settings + //FIX ME + /* if (w_headPlot != null) { Intensity(hpIntensityLoad); w_headPlot.cp5_widget.getController("Intensity").getCaptionLabel().setText(hpIntensityArray[hpIntensityLoad]); @@ -753,8 +756,12 @@ class SessionSettings { //Force redraw headplot on load. Fixes issue where heaplot draws outside of the widget. w_headPlot.headPlot.setPositionSize(w_headPlot.headPlot.hp_x, w_headPlot.headPlot.hp_y, w_headPlot.headPlot.hp_w, w_headPlot.headPlot.hp_h, w_headPlot.headPlot.hp_win_x, w_headPlot.headPlot.hp_win_y); } + */ ////////////////////////////Apply Band Power settings + + //FIX ME + /* try { //apply channel checkbox settings w_bandPower.bpChanSelect.deactivateAllButtons();; @@ -771,15 +778,17 @@ class SessionSettings { w_bandPower.cp5_widget.getController("bpAutoCleanThresholdDropdown").getCaptionLabel().setText(w_bandPower.getAutoCleanThreshold().getString()); w_bandPower.setAutoCleanTimer(loadBPAutoCleanTimer); w_bandPower.cp5_widget.getController("bpAutoCleanTimerDropdown").getCaptionLabel().setText(w_bandPower.getAutoCleanTimer().getString()); + */ ////////////////////////////Apply Spectrogram settings //Apply Max Freq dropdown SpectrogramMaxFreq(spectMaxFrqLoad); - w_spectrogram.cp5_widget.getController("SpectrogramMaxFreq").getCaptionLabel().setText(spectMaxFrqArray[spectMaxFrqLoad]); + w_spectrogram.cp5_widget.getController("SpectrogramMaxFreq").getCaptionLabel().setText(spectMaxFrqArray[spectMaxFrqLoad]); SpectrogramSampleRate(spectSampleRateLoad); - w_spectrogram.cp5_widget.getController("SpectrogramSampleRate").getCaptionLabel().setText(spectSampleRateArray[spectSampleRateLoad]); + w_spectrogram.cp5_widget.getController("SpectrogramSampleRate").getCaptionLabel().setText(spectSampleRateArray[spectSampleRateLoad]); SpectrogramLogLin(spectLogLinLoad); - w_spectrogram.cp5_widget.getController("SpectrogramLogLin").getCaptionLabel().setText(fftLogLinArray[spectLogLinLoad]); + //FIX ME + //w_spectrogram.cp5_widget.getController("SpectrogramLogLin").getCaptionLabel().setText(fftLogLinArray[spectLogLinLoad]); try { //apply channel checkbox settings w_spectrogram.spectChanSelectTop.deactivateAllButtons(); diff --git a/OpenBCI_GUI/W_BandPower.pde b/OpenBCI_GUI/W_BandPower.pde index 6bede4e4d..b36126452 100644 --- a/OpenBCI_GUI/W_BandPower.pde +++ b/OpenBCI_GUI/W_BandPower.pde @@ -10,130 +10,10 @@ // // // Created by: Wangshu Sun, May 2017 // // Modified by: Richard Waltman, March 2022 // +// Refactored by: Richard Waltman, March 2025 // // // //////////////////////////////////////////////////////////////////////////////////////////////////////// -public enum BPAutoClean implements IndexingInterface -{ - ON (0, "On"), - OFF (1, "Off"); - - private int index; - private String label; - private static BPAutoClean[] vals = values(); - - BPAutoClean(int _index, String _label) { - this.index = _index; - this.label = _label; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } -} - -public enum BPAutoCleanThreshold implements IndexingInterface -{ - FORTY (0, 40f, "40 uV"), - FIFTY (1, 50f, "50 uV"), - SIXTY (2, 60f, "60 uV"), - SEVENTY (3, 70f, "70 uV"), - EIGHTY (4, 80f, "80 uV"), - NINETY (5, 90f, "90 uV"), - ONE_HUNDRED(6, 100f, "100 uV"); - - private int index; - private float value; - private String label; - private static BPAutoCleanThreshold[] vals = values(); - - BPAutoCleanThreshold(int _index, float _value, String _label) { - this.index = _index; - this.value = _value; - this.label = _label; - } - - public float getValue() { - return value; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } -} - -public enum BPAutoCleanTimer implements IndexingInterface -{ - HALF_SECOND (0, 500, ".5 sec"), - ONE_SECOND (1, 1000, "1 sec"), - THREE_SECONDS (2, 2000, "3 sec"), - FIVE_SECONDS (3, 5000, "5 sec"), - TEN_SECONDS (4, 10000, "10 sec"), - TWENTY_SECONDS (5, 20000, "20 sec"), - THIRTY_SECONDS(6, 30000, "30 sec"); - - private int index; - private float value; - private String label; - private static BPAutoCleanTimer[] vals = values(); - - BPAutoCleanTimer(int _index, float _value, String _label) { - this.index = _index; - this.value = _value; - this.label = _label; - } - - public float getValue() { - return value; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } -} - class W_BandPower extends Widget { // indexes @@ -153,9 +33,10 @@ class W_BandPower extends Widget { private List cp5ElementsToCheck = new ArrayList(); - BPAutoClean bpAutoClean = BPAutoClean.OFF; - BPAutoCleanThreshold bpAutoCleanThreshold = BPAutoCleanThreshold.FIFTY; - BPAutoCleanTimer bpAutoCleanTimer = BPAutoCleanTimer.THREE_SECONDS; + BPAutoClean autoClean = BPAutoClean.OFF; + BPAutoCleanThreshold autoCleanThreshold = BPAutoCleanThreshold.FIFTY; + BPAutoCleanTimer autoCleanTimer = BPAutoCleanTimer.THREE_SECONDS; + int[] autoCleanTimers; boolean[] previousThresholdCrossed; @@ -173,13 +54,15 @@ class W_BandPower extends Widget { //Add settings dropdowns //Note: This is the correct way to create a dropdown using an enum -RW - addDropdown("bpAutoCleanDropdown", "AutoClean", bpAutoClean.getEnumStringsAsList(), bpAutoClean.getIndex()); - addDropdown("bpAutoCleanThresholdDropdown", "Threshold", bpAutoCleanThreshold.getEnumStringsAsList(), bpAutoCleanThreshold.getIndex()); - addDropdown("bpAutoCleanTimerDropdown", "Timer", bpAutoCleanTimer.getEnumStringsAsList(), bpAutoCleanTimer.getIndex()); + addDropdown("bandPowerAutoCleanDropdown", "AutoClean", autoClean.getEnumStringsAsList(), autoClean.getIndex()); + addDropdown("bandPowerAutoCleanThresholdDropdown", "Threshold", autoCleanThreshold.getEnumStringsAsList(), autoCleanThreshold.getIndex()); + addDropdown("bandPowerAutoCleanTimerDropdown", "Timer", autoCleanTimer.getEnumStringsAsList(), autoCleanTimer.getIndex()); //Note: This is a legacy way to create a dropdown which is sloppy and disorganized -RW //These two dropdowns also have to mirror the settings in the FFT widget - addDropdown("Smoothing", "Smooth", Arrays.asList(settings.fftSmoothingArray), smoothFac_ind); //smoothFac_ind is a global variable at the top of W_HeadPlot.pde - addDropdown("UnfiltFilt", "Filters?", Arrays.asList(settings.fftFilterArray), settings.fftFilterSave); + FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); + FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); + addDropdown("bandPowerSmoothingDropdown", "Smooth", smoothingFactor.getEnumStringsAsList(), smoothingFactor.getIndex()); + addDropdown("bandPowerDataFilteringDropdown", "Filtered?", filteredEnum.getEnumStringsAsList(), filteredEnum.getIndex()); // Setup for the BandPower plot bp_plot = new GPlot(_parent, x, y-navHeight, w, h+navHeight); @@ -302,14 +185,14 @@ class W_BandPower extends Widget { } private void autoCleanByEnableDisableChannels() { - if (bpAutoClean == BPAutoClean.OFF) { + if (autoClean == BPAutoClean.OFF) { return; } int numChannels = currentBoard.getNumEXGChannels(); for (int i = 0; i < numChannels; i++) { float uvrms = dataProcessing.data_std_uV[i]; - boolean thresholdCrossed = uvrms > bpAutoCleanThreshold.getValue(); + boolean thresholdCrossed = uvrms > autoCleanThreshold.getValue(); int currentMillis = millis(); @@ -320,7 +203,7 @@ class W_BandPower extends Widget { } //Auto-disable a channel if it's above the threshold and has been for the timer duration - boolean timerDurationExceeded = currentMillis - autoCleanTimers[i] > bpAutoCleanTimer.getValue(); + boolean timerDurationExceeded = currentMillis - autoCleanTimers[i] > autoCleanTimer.getValue(); if (timerDurationExceeded) { boolean enableChannel = !thresholdCrossed; bpChanSelect.setToggleState(i, enableChannel); @@ -329,44 +212,42 @@ class W_BandPower extends Widget { } public BPAutoClean getAutoClean() { - return bpAutoClean; + return autoClean; } public BPAutoCleanThreshold getAutoCleanThreshold() { - return bpAutoCleanThreshold; + return autoCleanThreshold; } public BPAutoCleanTimer getAutoCleanTimer() { - return bpAutoCleanTimer; + return autoCleanTimer; } public void setAutoClean(int n) { - bpAutoClean = bpAutoClean.values()[n]; + autoClean = autoClean.values()[n]; Arrays.fill(previousThresholdCrossed, false); Arrays.fill(autoCleanTimers, 0); } public void setAutoCleanThreshold(int n) { - bpAutoCleanThreshold = bpAutoCleanThreshold.values()[n]; + autoCleanThreshold = autoCleanThreshold.values()[n]; } public void setAutoCleanTimer(int n) { - bpAutoCleanTimer = bpAutoCleanTimer.values()[n]; + autoCleanTimer = autoCleanTimer.values()[n]; } + //Called in DataProcessing.pde to update data even if widget is closed public void updateBandPowerWidgetData() { float normalizingSum = 0; for (int i = 0; i < NUM_BANDS; i++) { float sum = 0; - for (int j = 0; j < bpChanSelect.getActiveChannels().size(); j++) { int chan = bpChanSelect.getActiveChannels().get(j); sum += dataProcessing.avgPowerInBins[chan][i]; } - activePower[i] = sum / bpChanSelect.getActiveChannels().size(); - normalizingSum += activePower[i]; } @@ -376,14 +257,24 @@ class W_BandPower extends Widget { } }; -public void bpAutoCleanDropdown(int n) { +public void bandPowerAutoCleanDropdown(int n) { w_bandPower.setAutoClean(n); } -public void bpAutoCleanThresholdDropdown(int n) { +public void bandPowerAutoCleanThresholdDropdown(int n) { w_bandPower.setAutoCleanThreshold(n); } -public void bpAutoCleanTimerDropdown(int n) { +public void bandPowerAutoCleanTimerDropdown(int n) { w_bandPower.setAutoCleanTimer(n); } + +public void bandPowerSmoothingDropdown(int n) { + globalFFTSettings.setSmoothingFactor(FFTSmoothingFactor.values()[n]); + //FIX ME TO UPDATE THE FFT WIDGET DROPDOWN ALSO +} + +public void bandPowerDataFilteringDropdown(int n) { + globalFFTSettings.setFilteredEnum(FFTFilteredEnum.values()[n]); + //FIX ME TO UPDATE THE FFT WIDGET DROPDOWN ALSO +} \ No newline at end of file diff --git a/OpenBCI_GUI/W_FFT.pde b/OpenBCI_GUI/W_FFT.pde index d564f8fee..a9b94a81e 100644 --- a/OpenBCI_GUI/W_FFT.pde +++ b/OpenBCI_GUI/W_FFT.pde @@ -5,6 +5,7 @@ // It extends the Widget class // // Conor Russomanno, November 2016 +// Refactored: Richard Waltman, March 2025 // // Requires the plotting library from grafica ... // replacing the old gwoptics (which is now no longer supported) @@ -14,104 +15,90 @@ class W_fft extends Widget { public ExGChannelSelect fftChanSelect; - boolean prevChanSelectIsVisible = false; + private boolean prevChanSelectIsVisible = false; - GPlot fft_plot; //create an fft plot for each active channel - GPointsArray[] fft_points; + private GPlot fftPlot; //create an fft plot for each active channel + private GPointsArray[] fftGplotPoints; - int[] xLimOptions = {20, 40, 60, 100, 120, 250, 500, 800}; - int[] yLimOptions = {10, 50, 100, 1000}; + private FFTMaxFrequency maxFrequency = FFTMaxFrequency.MAX_60; + private FFTVerticalScale verticalScale = FFTVerticalScale.SCALE_100; + private FFTLogLin logLin = FFTLogLin.LOG; - int xLim = xLimOptions[2]; //maximum value of x axis ... in this case 20 Hz, 40 Hz, 60 Hz, 120 Hz - int xMax = xLimOptions[xLimOptions.length-1]; //maximum possible frequency in FFT - int FFT_indexLim = int(1.0*xMax*(getNfftSafe()/currentBoard.getSampleRate())); // maxim value of FFT index - int yLim = yLimOptions[2]; //maximum value of y axis ... 100 uV + private final int FFT_FREQUENCY_LIMIT = int(1.0 * maxFrequency.getHighestFrequency() * (getNumFFTPoints() / currentBoard.getSampleRate())); List cp5ElementsToCheck = new ArrayList(); W_fft(PApplet _parent){ - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + super(_parent); - //Default FFT plot settings - settings.fftMaxFrqSave = 2; - settings.fftMaxuVSave = 2; - settings.fftLogLinSave = 0; - settings.fftSmoothingSave = 3; - settings.fftFilterSave = 0; - - //Instantiate Channel Select Class fftChanSelect = new ExGChannelSelect(pApplet, x, y, w, navH); fftChanSelect.activateAllButtons(); cp5ElementsToCheck.addAll(fftChanSelect.getCp5ElementsForOverlapCheck()); - //This is the protocol for setting up dropdowns. - //Note that these 3 dropdowns correspond to the 3 global functions below - //You just need to make sure the "id" (the 1st String) has the same name as the corresponding function - addDropdown("MaxFreq", "Max Freq", Arrays.asList(settings.fftMaxFrqArray), settings.fftMaxFrqSave); - addDropdown("VertScale", "Max uV", Arrays.asList(settings.fftVertScaleArray), settings.fftMaxuVSave); - addDropdown("LogLin", "Log/Lin", Arrays.asList(settings.fftLogLinArray), settings.fftLogLinSave); - addDropdown("Smoothing", "Smooth", Arrays.asList(settings.fftSmoothingArray), smoothFac_ind); //smoothFac_ind is a global variable at the top of W_HeadPlot.pde - addDropdown("UnfiltFilt", "Filters?", Arrays.asList(settings.fftFilterArray), settings.fftFilterSave); - - fft_points = new GPointsArray[globalChannelCount]; - // println("fft_points.length: " + fft_points.length); + addDropdown("fftMaxFrequencyDropdown", "Max Hz", maxFrequency.getEnumStringsAsList(), maxFrequency.getIndex()); + addDropdown("fftVerticalScaleDropdown", "Max uV", verticalScale.getEnumStringsAsList(), verticalScale.getIndex()); + addDropdown("fftLogLinDropdown", "Log/Lin", logLin.getEnumStringsAsList(), logLin.getIndex()); + FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); + FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); + addDropdown("fftSmoothingDropdown", "Smooth", smoothingFactor.getEnumStringsAsList(), smoothingFactor.getIndex()); + addDropdown("fftFilteringDropdown", "Filters?", filteredEnum.getEnumStringsAsList(), filteredEnum.getIndex()); + + fftGplotPoints = new GPointsArray[globalChannelCount]; initializeFFTPlot(_parent); } void initializeFFTPlot(PApplet _parent) { //setup GPlot for FFT - fft_plot = new GPlot(_parent, x, y-navHeight, w, h+navHeight); //based on container dimensions - fft_plot.setAllFontProperties("Arial", 0, 14); - fft_plot.getXAxis().setAxisLabelText("Frequency (Hz)"); - fft_plot.getYAxis().setAxisLabelText("Amplitude (uV)"); - fft_plot.setMar(60, 70, 40, 30); //{ bot=60, left=70, top=40, right=30 } by default - String logScale = settings.fftLogLinSave == 0 ? "y" : ""; - fft_plot.setLogScale(logScale); - - fft_plot.setYLim(0.1, yLim); - //int _nTicks = int(yLim/10 - 1); //number of axis subdivisions + fftPlot = new GPlot(_parent, x, y-navHeight, w, h+navHeight); + fftPlot.setAllFontProperties("Arial", 0, 14); + fftPlot.getXAxis().setAxisLabelText("Frequency (Hz)"); + fftPlot.getYAxis().setAxisLabelText("Amplitude (uV)"); + fftPlot.setMar(60, 70, 40, 30); //{ bot=60, left=70, top=40, right=30 } by default + setPlotLogScale(); + + fftPlot.setYLim(0.1, verticalScale.getValue()); int _nTicks = 10; - fft_plot.getYAxis().setNTicks(_nTicks); //sets the number of axis divisions... - fft_plot.setXLim(0.1, xLim); - fft_plot.getYAxis().setDrawTickLabels(true); - fft_plot.setPointSize(2); - fft_plot.setPointColor(0); - fft_plot.getXAxis().setFontColor(OPENBCI_DARKBLUE); - fft_plot.getXAxis().setLineColor(OPENBCI_DARKBLUE); - fft_plot.getXAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); - fft_plot.getYAxis().setFontColor(OPENBCI_DARKBLUE); - fft_plot.getYAxis().setLineColor(OPENBCI_DARKBLUE); - fft_plot.getYAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); + fftPlot.getYAxis().setNTicks(_nTicks); //sets the number of axis divisions... + fftPlot.setXLim(0.1, maxFrequency.getValue()); + fftPlot.getYAxis().setDrawTickLabels(true); + fftPlot.setPointSize(2); + fftPlot.setPointColor(0); + fftPlot.getXAxis().setFontColor(OPENBCI_DARKBLUE); + fftPlot.getXAxis().setLineColor(OPENBCI_DARKBLUE); + fftPlot.getXAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); + fftPlot.getYAxis().setFontColor(OPENBCI_DARKBLUE); + fftPlot.getYAxis().setLineColor(OPENBCI_DARKBLUE); + fftPlot.getYAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); //setup points of fft point arrays - for (int i = 0; i < fft_points.length; i++) { - fft_points[i] = new GPointsArray(FFT_indexLim); + for (int i = 0; i < fftGplotPoints.length; i++) { + fftGplotPoints[i] = new GPointsArray(FFT_FREQUENCY_LIMIT); } //fill fft point arrays - for (int i = 0; i < fft_points.length; i++) { //loop through each channel - for (int j = 0; j < FFT_indexLim; j++) { + for (int i = 0; i < fftGplotPoints.length; i++) { //loop through each channel + for (int j = 0; j < FFT_FREQUENCY_LIMIT; j++) { GPoint temp = new GPoint(j, 0); - fft_points[i].set(j, temp); + fftGplotPoints[i].set(j, temp); } } //map fft point arrays to fft plots - fft_plot.setPoints(fft_points[0]); + fftPlot.setPoints(fftGplotPoints[0]); } void update(){ super.update(); //calls the parent update() method of Widget (DON'T REMOVE) - float sr = currentBoard.getSampleRate(); - int nfft = getNfftSafe(); + float sampleRate = currentBoard.getSampleRate(); + int fftPointCount = getNumFFTPoints(); //update the points of the FFT channel arrays for all channels - for (int i = 0; i < fft_points.length; i++) { - for (int j = 0; j < FFT_indexLim + 2; j++) { //loop through frequency domain data, and store into points array - GPoint powerAtBin = new GPoint((1.0*sr/nfft)*j, fftBuff[i].getBand(j)); - fft_points[i].set(j, powerAtBin); + for (int i = 0; i < fftGplotPoints.length; i++) { + for (int j = 0; j < FFT_FREQUENCY_LIMIT + 2; j++) { //loop through frequency domain data, and store into points array + GPoint powerAtBin = new GPoint((1.0*sampleRate/fftPointCount)*j, fftBuff[i].getBand(j)); + fftGplotPoints[i].set(j, powerAtBin); } } @@ -137,21 +124,21 @@ class W_fft extends Widget { //draw FFT Graph w/ all plots noStroke(); - fft_plot.beginDraw(); - fft_plot.drawBackground(); - fft_plot.drawBox(); - fft_plot.drawXAxis(); - fft_plot.drawYAxis(); - fft_plot.drawGridLines(GPlot.BOTH); + fftPlot.beginDraw(); + fftPlot.drawBackground(); + fftPlot.drawBox(); + fftPlot.drawXAxis(); + fftPlot.drawYAxis(); + fftPlot.drawGridLines(GPlot.BOTH); //Update and draw active channels that have been selected via channel select for this widget for (int j = 0; j < fftChanSelect.getActiveChannels().size(); j++) { int chan = fftChanSelect.getActiveChannels().get(j); - fft_plot.setLineColor((int)channelColors[chan % 8]); + fftPlot.setLineColor((int)channelColors[chan % 8]); //remap fft point arrays to fft plots - fft_plot.setPoints(fft_points[chan]); - fft_plot.drawLines(); + fftPlot.setPoints(fftGplotPoints[chan]); + fftPlot.drawLines(); } - fft_plot.endDraw(); + fftPlot.endDraw(); //for this widget need to redraw the grey bar, bc the FFT plot covers it up... fill(200, 200, 200); @@ -181,64 +168,57 @@ class W_fft extends Widget { void flexGPlotSizeAndPosition() { if (fftChanSelect.isVisible()) { - fft_plot.setPos(x, y + fftChanSelect.getHeight() - navH); - fft_plot.setOuterDim(w, h - fftChanSelect.getHeight() + navH); + fftPlot.setPos(x, y + fftChanSelect.getHeight() - navH); + fftPlot.setOuterDim(w, h - fftChanSelect.getHeight() + navH); } else { - fft_plot.setPos(x, y - navH); - fft_plot.setOuterDim(w, h + navH); + fftPlot.setPos(x, y - navH); + fftPlot.setOuterDim(w, h + navH); } } -}; -//These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected -//triggered when there is an event in the MaxFreq. Dropdown -void MaxFreq(int n) { - /* request the selected item based on index n */ - w_fft.fft_plot.setXLim(0.1, w_fft.xLimOptions[n]); //update the xLim of the FFT_Plot - settings.fftMaxFrqSave = n; //save the xLim to variable for save/load settings -} + public void setMaxFrequency(int n) { + maxFrequency = FFTMaxFrequency.values[n]; + fftPlot.setXLim(0.1, maxFrequency.getValue()); + } -//triggered when there is an event in the VertScale Dropdown -void VertScale(int n) { + public void setVerticalScale(int n) { + verticalScale = FFTVerticalScale.values[n]; + fftPlot.setYLim(0.1, verticalScale.getValue()); + } - w_fft.fft_plot.setYLim(0.1, w_fft.yLimOptions[n]); //update the yLim of the FFT_Plot - settings.fftMaxuVSave = n; //save the yLim to variable for save/load settings -} + public void setLogLin(int n) { + logLin = FFTLogLin.values[n]; + setPlotLogScale(); + } -//triggered when there is an event in the LogLin Dropdown -void LogLin(int n) { - if (n==0) { - w_fft.fft_plot.setLogScale("y"); - //store the current setting to save - settings.fftLogLinSave = 0; - } else { - w_fft.fft_plot.setLogScale(""); - //store the current setting to save - settings.fftLogLinSave = 1; + private void setPlotLogScale() { + if (logLin == FFTLogLin.LOG) { + fftPlot.setLogScale("y"); + } else { + fftPlot.setLogScale(""); + } } +}; + +//These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected +public void fftMaxFrequencyDropdown(int n) { + w_fft.setMaxFrequency(n); } -//triggered when there is an event in the Smoothing Dropdown -void Smoothing(int n) { - smoothFac_ind = n; - settings.fftSmoothingSave = n; - //since this function is called by both the BandPower and FFT Widgets the dropdown needs to be updated in both - w_fft.cp5_widget.getController("Smoothing").getCaptionLabel().setText(settings.fftSmoothingArray[n]); - w_bandPower.cp5_widget.getController("Smoothing").getCaptionLabel().setText(settings.fftSmoothingArray[n]); +public void fftVerticalScaleDropdown(int n) { + w_fft.setVerticalScale(n); +} +public void fftLogLinDropdown(int n) { + w_fft.setLogLin(n); } -//triggered when there is an event in the UnfiltFilt Dropdown -void UnfiltFilt(int n) { - settings.fftFilterSave = n; - if (n==0) { - //have FFT use filtered data -- default - isFFTFiltered = true; - } else { - //have FFT use unfiltered data - isFFTFiltered = false; - } - //since this function is called by both the BandPower and FFT Widgets the dropdown needs to be updated in both - w_fft.cp5_widget.getController("UnfiltFilt").getCaptionLabel().setText(settings.fftFilterArray[n]); - w_bandPower.cp5_widget.getController("UnfiltFilt").getCaptionLabel().setText(settings.fftFilterArray[n]); +public void fftSmoothingDropdown(int n) { + globalFFTSettings.setSmoothingFactor(FFTSmoothingFactor.values()[n]); + //FIX ME TO UPDATE THE BAND POWER WIDGET DROPDOWN ALSO } + +public void fftFilteringDropdown(int n) { + globalFFTSettings.setFilteredEnum(FFTFilteredEnum.values()[n]); + //FIX ME TO UPDATE THE BAND POWER WIDGET DROPDOWN ALSO +} \ No newline at end of file diff --git a/OpenBCI_GUI/W_HeadPlot.pde b/OpenBCI_GUI/W_HeadPlot.pde index d7578b3f6..c34698414 100644 --- a/OpenBCI_GUI/W_HeadPlot.pde +++ b/OpenBCI_GUI/W_HeadPlot.pde @@ -8,56 +8,54 @@ // // Created by: Conor Russomanno, November 2016 // Based on code written by: Chip Audette, Oct 2013 +// Refactored by: Richard Waltman, March 2025 // ///////////////////////////////////////////////////, -float[] smoothFac = new float[]{0.0, 0.5, 0.75, 0.9, 0.95, 0.98, 0.99, 0.999}; //used by FFT & Headplot -int smoothFac_ind = 3; //initial index into the smoothFac array = 0.75 to start .. used by FFT & Head Plots +// ----- these variable/methods are used for adjusting the intensity factor of the headplot opacity --------------------------------------------------------------------------------------------------------- +//float default_vertScale_uV = 200.0; +//float[] vertScaleFactor = { 0.25f, 0.5f, 1.0f, 2.0f, 5.0f, 50.0f}; class W_HeadPlot extends Widget { - HeadPlot headPlot; + + private HeadPlot headPlot; + + private HeadPlotIntensity headPlotIntensity = HeadPlotIntensity.INTENSITY_1; + private HeadPlotPolarity headPlotPolarity = HeadPlotPolarity.PLUS_AND_MINUS; + private HeadPlotContours headPlotContours = HeadPlotContours.ON; + private HeadPlotSmoothing headPlotSmoothing = HeadPlotSmoothing.SMOOTH_98; + + private final float DEFAULT_VERTICAL_SCALE_UV = 200f; //this defines the Y-scale on the montage plots...this is the vertical space between traces W_HeadPlot(PApplet _parent){ - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) - - //Headplot settings - settings.hpIntensitySave = 2; - settings.hpPolaritySave = 0; - settings.hpContoursSave = 0; - settings.hpSmoothingSave = 3; - //This is the protocol for setting up dropdowns. - //Note that these 3 dropdowns correspond to the 3 global functions below - //You just need to make sure the "id" (the 1st String) has the same name as the corresponding function - // addDropdown("Ten20", "Layout", Arrays.asList("10-20", "5-10"), 0); - // addDropdown("Headset", "Headset", Arrays.asList("None", "Mark II", "Mark III", "Mark IV "), 0); - addDropdown("Intensity", "Intensity", Arrays.asList("4x", "2x", "1x", "0.5x", "0.2x", "0.02x"), vertScaleFactor_ind); - addDropdown("Polarity", "Polarity", Arrays.asList("+/-", " + "), settings.hpPolaritySave); - addDropdown("ShowContours", "Contours", Arrays.asList("ON", "OFF"), settings.hpContoursSave); - addDropdown("SmoothingHeadPlot", "Smooth", Arrays.asList(settings.fftSmoothingArray), smoothFac_ind); - //Initialize the headplot + super(_parent); + + addDropdown("headPlotIntensityDropdown", "Intensity", headPlotIntensity.getEnumStringsAsList(), headPlotIntensity.getIndex()); + addDropdown("headPlotPolarityDropdown", "Polarity", headPlotPolarity.getEnumStringsAsList(), headPlotPolarity.getIndex()); + addDropdown("headPlotContoursDropdown", "Contours", headPlotContours.getEnumStringsAsList(), headPlotContours.getIndex()); + addDropdown("headPlotSmoothingDropdown", "Smooth", headPlotSmoothing.getEnumStringsAsList(), headPlotSmoothing.getIndex()); + updateHeadPlot(); } - void updateHeadPlot() { + private void updateHeadPlot() { headPlot = new HeadPlot(x, y, w, h, win_w, win_h); - //FROM old Gui_Manager headPlot.setIntensityData_byRef(dataProcessing.data_std_uV, is_railed); headPlot.setPolarityData_byRef(dataProcessing.polarity); - setSmoothFac(smoothFac[smoothFac_ind]); } - void update(){ - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + public void update(){ + super.update(); headPlot.update(); } - void draw(){ + public void draw(){ super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) headPlot.draw(); //draw the actual headplot } - void screenResized(){ + public void screenResized(){ super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) headPlot.hp_x = x; headPlot.hp_y = y; @@ -66,88 +64,69 @@ class W_HeadPlot extends Widget { headPlot.hp_win_x = x; headPlot.hp_win_y = y; - thread("doHardCalcs"); + thread("doHardCalculations"); } - void mousePressed(){ + public void mousePressed(){ super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) headPlot.mousePressed(); } - void mouseReleased(){ + public void mouseReleased(){ super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) headPlot.mouseReleased(); } - void mouseDragged(){ + public void mouseDragged(){ super.mouseDragged(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) headPlot.mouseDragged(); } - //add custom class functions here - void setSmoothFac(float fac) { - headPlot.smooth_fac = fac; + public void setIntensity(int n) { + headPlotIntensity = HeadPlotIntensity.values[n]; + float maxIntensityUv = DEFAULT_VERTICAL_SCALE_UV * headPlotIntensity.getValue(); + headPlot.setMaxIntensity_uV(maxIntensityUv); } -}; -//triggered when there is an event in the Polarity Dropdown -void Polarity(int n) { + public void setPolarity(int n) { + headPlotPolarity = HeadPlotPolarity.values[n]; + headPlot.use_polarity = headPlotPolarity == HeadPlotPolarity.PLUS_AND_MINUS; + } - if (n==0) { - w_headPlot.headPlot.use_polarity = true; - } else { - w_headPlot.headPlot.use_polarity = false; + public void setContours(int n) { + headPlotContours = HeadPlotContours.values[n]; + headPlot.drawHeadAsContours = headPlotContours == HeadPlotContours.ON; } - settings.hpPolaritySave = n; -} -void ShowContours(int n){ - if(n==0){ - //turn headplot contours on - w_headPlot.headPlot.drawHeadAsContours = true; - } else if(n==1){ - //turn headplot contours off - w_headPlot.headPlot.drawHeadAsContours = false; + public void setSmoothing(int n) { + headPlotSmoothing = HeadPlotSmoothing.values[n]; + headPlot.smoothingFactor = headPlotSmoothing.getValue(); } - settings.hpContoursSave = n; -} -//triggered when there is an event in the SmoothingHeadPlot Dropdown -void SmoothingHeadPlot(int n) { - w_headPlot.setSmoothFac(smoothFac[n]); - settings.hpSmoothingSave = n; -} + private void doHardCalculations() { + if (!headPlot.threadLock) { + headPlot.threadLock = true; + headPlot.setPositionSize(w_headPlot.headPlot.hp_x, w_headPlot.headPlot.hp_y, w_headPlot.headPlot.hp_w, w_headPlot.headPlot.hp_h, w_headPlot.headPlot.hp_win_x, w_headPlot.headPlot.hp_win_y); + headPlot.hardCalcsDone = true; + headPlot.threadLock = false; + } + } +}; -void Intensity(int n){ - vertScaleFactor_ind = n; - updateVertScale(); - settings.hpIntensitySave = n; +public void headPlotIntensityDropdown(int n) { + w_headPlot.setIntensity(n); } -// ----- these variable/methods are used for adjusting the intensity factor of the headplot opacity --------------------------------------------------------------------------------------------------------- -float default_vertScale_uV = 200.0; //this defines the Y-scale on the montage plots...this is the vertical space between traces -float[] vertScaleFactor = { 0.25f, 0.5f, 1.0f, 2.0f, 5.0f, 50.0f}; -int vertScaleFactor_ind = 2; -float vertScale_uV = default_vertScale_uV; - -void setVertScaleFactor_ind(int ind) { - vertScaleFactor_ind = max(0,ind); - if (ind >= vertScaleFactor.length) vertScaleFactor_ind = 0; - updateVertScale(); +public void headPlotPolarityDropdown(int n) { + w_headPlot.setPolarity(n); } -void updateVertScale() { - vertScale_uV = default_vertScale_uV * vertScaleFactor[vertScaleFactor_ind]; - w_headPlot.headPlot.setMaxIntensity_uV(vertScale_uV); +public void headPlotContoursDropdown(int n) { + w_headPlot.setContours(n); } -void doHardCalcs() { - if (!w_headPlot.headPlot.threadLock) { - w_headPlot.headPlot.threadLock = true; - w_headPlot.headPlot.setPositionSize(w_headPlot.headPlot.hp_x, w_headPlot.headPlot.hp_y, w_headPlot.headPlot.hp_w, w_headPlot.headPlot.hp_h, w_headPlot.headPlot.hp_win_x, w_headPlot.headPlot.hp_win_y); - w_headPlot.headPlot.hardCalcsDone = true; - w_headPlot.headPlot.threadLock = false; - } +public void headPlotSmoothingDropdown(int n) { + w_headPlot.setSmoothing(n); } //--------------------------------------------------------------------------------------------------------------------------------------- @@ -190,7 +169,7 @@ class HeadPlot { private int image_x, image_y; public boolean drawHeadAsContours; private boolean plot_color_as_log = true; - public float smooth_fac = 0.0f; + public float smoothingFactor = 0.0f; private boolean use_polarity = true; private int mouse_over_elec_index = -1; private boolean isDragging = false; @@ -238,12 +217,12 @@ class HeadPlot { } public void setIntensityData_byRef(float[] data, DataStatus[] is_rail) { - intensity_data_uV = data; //simply alias the data held externally. DOES NOT COPY THE DATA ITSEF! IT'S SIMPLY LINKED! + intensity_data_uV = data; //simply alias the data held externally. DOES NOT COPY THE DATA ITSELF! IT'S SIMPLY LINKED! is_railed = is_rail; } public void setPolarityData_byRef(float[] data) { - polarity_data = data;//simply alias the data held externally. DOES NOT COPY THE DATA ITSEF! IT'S SIMPLY LINKED! + polarity_data = data;//simply alias the data held externally. DOES NOT COPY THE DATA ITSELF! IT'S SIMPLY LINKED! } public String getUsePolarityTrueFalse() { @@ -1033,7 +1012,7 @@ class HeadPlot { } //smooth in time - if (smooth_fac > 0.0f) voltage = smooth_fac*prev_val + (1.0-smooth_fac)*voltage; + if (smoothingFactor > 0.0f) voltage = smoothingFactor*prev_val + (1.0-smoothingFactor)*voltage; return voltage; } diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index 1d349b0de..52c5c4df9 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -63,6 +63,8 @@ class W_Spectrogram extends Widget { float[] topFFTAvg; float[] botFFTAvg; + private FFTLogLin logLin = FFTLogLin.LIN; + W_Spectrogram(PApplet _parent) { super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) @@ -81,10 +83,11 @@ class W_Spectrogram extends Widget { graphY = y + paddingTop; graphW = w - paddingRight - paddingLeft; graphH = h - paddingBottom - paddingTop; - + + //FIX ME REMOVE THESE settings.spectMaxFrqSave = 2; settings.spectSampleRateSave = 4; - settings.spectLogLinSave = 1; + vertAxisLabel = vertAxisLabels[settings.spectMaxFrqSave]; horizAxisLabel = horizAxisLabels[settings.spectSampleRateSave]; horizAxisLabelStrings = new StringList(); @@ -96,7 +99,7 @@ class W_Spectrogram extends Widget { //You just need to make sure the "id" (the 1st String) has the same name as the corresponding function addDropdown("SpectrogramMaxFreq", "Max Freq", Arrays.asList(settings.spectMaxFrqArray), settings.spectMaxFrqSave); addDropdown("SpectrogramSampleRate", "Window", Arrays.asList(settings.spectSampleRateArray), settings.spectSampleRateSave); - addDropdown("SpectrogramLogLin", "Log/Lin", Arrays.asList(settings.fftLogLinArray), settings.spectLogLinSave); + addDropdown("SpectrogramLogLin", "Log/Lin", logLin.getEnumStringsAsList(), logLin.getIndex()); //Resize the height of the data image using default dataImageH = vertAxisLabel[0] * 2; @@ -186,7 +189,7 @@ class W_Spectrogram extends Widget { for (int i = 0; i <= dataImg.height/2; i++) { //LEFT SPECTROGRAM ON TOP float hueValue = hueLimit - map((fftAvgs(spectChanSelectTop.getActiveChannels(), i)*32), 0, 256, 0, hueLimit); - if (settings.spectLogLinSave == 0) { + if (logLin == FFTLogLin.LOG) { hueValue = map(log10(hueValue), 0, 2, 0, hueLimit); } // colorMode is HSB, the range for hue is 256, for saturation is 100, brightness is 100. @@ -205,7 +208,7 @@ class W_Spectrogram extends Widget { //RIGHT SPECTROGRAM ON BOTTOM hueValue = hueLimit - map((fftAvgs(spectChanSelectBot.getActiveChannels(), i)*32), 0, 256, 0, hueLimit); - if (settings.spectLogLinSave == 0) { + if (logLin == FFTLogLin.LOG) { hueValue = map(log10(hueValue), 0, 2, 0, hueLimit); } // colorMode is HSB, the range for hue is 256, for saturation is 100, brightness is 100. @@ -350,7 +353,7 @@ class W_Spectrogram extends Widget { //draw color scale reference to the right of the spectrogram for (int i = 0; i < colorScaleHeight; i++) { float hueValue = hueLimit - map(i * 2, 0, colorScaleHeight*2, 0, hueLimit); - if (settings.spectLogLinSave == 0) { + if (logLin == FFTLogLin.LOG) { hueValue = map(log(hueValue) / log(10), 0, 2, 0, hueLimit); } //println(hueValue); @@ -450,6 +453,10 @@ class W_Spectrogram extends Widget { dataImg.pixels[i] = color(0); // Black background } } + + public void setLogLin(int n) { + logLin = logLin.values()[n]; + } }; //These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected @@ -489,6 +496,6 @@ void SpectrogramSampleRate(int n) { w_spectrogram.fetchTimeStrings(w_spectrogram.numHorizAxisDivs); } -void SpectrogramLogLin(int n) { - settings.spectLogLinSave = n; +public void SpectrogramLogLin(int n) { + w_spectrogram.setLogLin(n); } \ No newline at end of file From 0db76715e85c1e8365b5df28f70e6d1d74ba5f92 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Fri, 4 Apr 2025 10:51:37 -0500 Subject: [PATCH 02/29] Fix FFT and BandPower widget dropdowns linked settings on frontend --- OpenBCI_GUI/FFTEnums.pde | 2 +- OpenBCI_GUI/W_BandPower.pde | 16 ++++++++++++++-- OpenBCI_GUI/W_FFT.pde | 16 ++++++++++++++-- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/OpenBCI_GUI/FFTEnums.pde b/OpenBCI_GUI/FFTEnums.pde index 3602b98bf..4fb69f2c7 100644 --- a/OpenBCI_GUI/FFTEnums.pde +++ b/OpenBCI_GUI/FFTEnums.pde @@ -77,7 +77,7 @@ public enum FFTSmoothingFactor implements IndexingInterface { // Used by FFT Widget and Band Power Widget public enum FFTFilteredEnum implements IndexingInterface { FILTERED (0, "Filtered"), - UNFILTERED (1, "Unfiltered"); + UNFILTERED (1, "Unfilt."); private final int index; private final String label; diff --git a/OpenBCI_GUI/W_BandPower.pde b/OpenBCI_GUI/W_BandPower.pde index b36126452..5a888ce70 100644 --- a/OpenBCI_GUI/W_BandPower.pde +++ b/OpenBCI_GUI/W_BandPower.pde @@ -255,6 +255,16 @@ class W_BandPower extends Widget { normalizedBandPowers[i] = activePower[i] / normalizingSum; } } + + public void setSmoothingDropdownFrontend(FFTSmoothingFactor _smoothingFactor) { + String s = _smoothingFactor.getString(); + cp5_widget.getController("bandPowerSmoothingDropdown").getCaptionLabel().setText(s); + } + + public void setFilteringDropdownFrontend(FFTFilteredEnum _filteredEnum) { + String s = _filteredEnum.getString(); + cp5_widget.getController("bandPowerDataFilteringDropdown").getCaptionLabel().setText(s); + } }; public void bandPowerAutoCleanDropdown(int n) { @@ -271,10 +281,12 @@ public void bandPowerAutoCleanTimerDropdown(int n) { public void bandPowerSmoothingDropdown(int n) { globalFFTSettings.setSmoothingFactor(FFTSmoothingFactor.values()[n]); - //FIX ME TO UPDATE THE FFT WIDGET DROPDOWN ALSO + FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); + w_fft.setSmoothingDropdownFrontend(smoothingFactor); } public void bandPowerDataFilteringDropdown(int n) { globalFFTSettings.setFilteredEnum(FFTFilteredEnum.values()[n]); - //FIX ME TO UPDATE THE FFT WIDGET DROPDOWN ALSO + FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); + w_fft.setFilteringDropdownFrontend(filteredEnum); } \ No newline at end of file diff --git a/OpenBCI_GUI/W_FFT.pde b/OpenBCI_GUI/W_FFT.pde index a9b94a81e..d475946d7 100644 --- a/OpenBCI_GUI/W_FFT.pde +++ b/OpenBCI_GUI/W_FFT.pde @@ -198,6 +198,16 @@ class W_fft extends Widget { fftPlot.setLogScale(""); } } + + public void setSmoothingDropdownFrontend(FFTSmoothingFactor _smoothingFactor) { + String s = _smoothingFactor.getString(); + cp5_widget.getController("fftSmoothingDropdown").getCaptionLabel().setText(s); + } + + public void setFilteringDropdownFrontend(FFTFilteredEnum _filteredEnum) { + String s = _filteredEnum.getString(); + cp5_widget.getController("fftFilteringDropdown").getCaptionLabel().setText(s); + } }; //These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected @@ -215,10 +225,12 @@ public void fftLogLinDropdown(int n) { public void fftSmoothingDropdown(int n) { globalFFTSettings.setSmoothingFactor(FFTSmoothingFactor.values()[n]); - //FIX ME TO UPDATE THE BAND POWER WIDGET DROPDOWN ALSO + FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); + w_bandPower.setSmoothingDropdownFrontend(smoothingFactor); } public void fftFilteringDropdown(int n) { globalFFTSettings.setFilteredEnum(FFTFilteredEnum.values()[n]); - //FIX ME TO UPDATE THE BAND POWER WIDGET DROPDOWN ALSO + FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); + w_bandPower.setFilteringDropdownFrontend(filteredEnum); } \ No newline at end of file From 31a20adf2cbdae8306190842778ea480e06290fc Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Tue, 8 Apr 2025 17:54:35 -0500 Subject: [PATCH 03/29] Add SpectrogramEnums.pde for spectrogram settings --- OpenBCI_GUI/SessionSettings.pde | 11 +-- OpenBCI_GUI/SpectrogramEnums.pde | 99 +++++++++++++++++++++ OpenBCI_GUI/W_Spectrogram.pde | 144 ++++++++++++------------------- 3 files changed, 157 insertions(+), 97 deletions(-) create mode 100644 OpenBCI_GUI/SpectrogramEnums.pde diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde index fe701fd21..76a3879e8 100644 --- a/OpenBCI_GUI/SessionSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -64,10 +64,6 @@ class SessionSettings { //Analog Read settings int arVertScaleSave; int arHorizScaleSave; - //Spectrogram Widget settings - //FIX ME REMOVE THIS - int spectMaxFrqSave; - int spectSampleRateSave; //default configuration settings file location and file name variables private String sessionPath = ""; @@ -98,10 +94,6 @@ class SessionSettings { String[] arVertScaleArray = {"Auto", "50", "100", "200", "400", "1000", "10000"}; String[] arHorizScaleArray = {"Sync", "1 sec", "3 sec", "5 sec", "10 sec", "20 sec"}; - //Used to set text in dropdown menus when loading Spectrogram Setings - String[] spectMaxFrqArray = {"20 Hz", "40 Hz", "60 Hz", "100 Hz", "120 Hz", "250 Hz"}; - String[] spectSampleRateArray = {"30 Min.", "6 Min.", "3 Min.", "1.5 Min.", "1 Min."}; - //Load Accel. dropdown variables int loadAccelVertScale; int loadAccelHorizScale; @@ -782,11 +774,14 @@ class SessionSettings { ////////////////////////////Apply Spectrogram settings //Apply Max Freq dropdown + //FIX ME + /* SpectrogramMaxFreq(spectMaxFrqLoad); w_spectrogram.cp5_widget.getController("SpectrogramMaxFreq").getCaptionLabel().setText(spectMaxFrqArray[spectMaxFrqLoad]); SpectrogramSampleRate(spectSampleRateLoad); w_spectrogram.cp5_widget.getController("SpectrogramSampleRate").getCaptionLabel().setText(spectSampleRateArray[spectSampleRateLoad]); SpectrogramLogLin(spectLogLinLoad); + */ //FIX ME //w_spectrogram.cp5_widget.getController("SpectrogramLogLin").getCaptionLabel().setText(fftLogLinArray[spectLogLinLoad]); try { diff --git a/OpenBCI_GUI/SpectrogramEnums.pde b/OpenBCI_GUI/SpectrogramEnums.pde new file mode 100644 index 000000000..82fbd0901 --- /dev/null +++ b/OpenBCI_GUI/SpectrogramEnums.pde @@ -0,0 +1,99 @@ +public enum SpectrogramMaxFrequency implements IndexingInterface { + MAX_20 (0, 20, "20 Hz", new int[]{20, 15, 10, 5, 0, 5, 10, 15, 20}), + MAX_40 (1, 40, "40 Hz", new int[]{40, 30, 20, 10, 0, 10, 20, 30, 40}), + MAX_60 (2, 60, "60 Hz", new int[]{60, 45, 30, 15, 0, 15, 30, 45, 60}), + MAX_100 (3, 100, "100 Hz", new int[]{100, 75, 50, 25, 0, 25, 50, 75, 100}), + MAX_120 (4, 120, "120 Hz", new int[]{120, 90, 60, 30, 0, 30, 60, 90, 120}), + MAX_250 (5, 250, "250 Hz", new int[]{250, 188, 125, 63, 0, 63, 125, 188, 250}); + + private final int index; + private final int value; + private final String label; + private final int[] axisLabels; + private static final SpectrogramMaxFrequency[] values = values(); + + SpectrogramMaxFrequency(int index, int value, String label, int[] axisLabels) { + this.index = index; + this.value = value; + this.label = label; + this.axisLabels = axisLabels; + } + + public int getValue() { + return value; + } + + public int[] getAxisLabels() { + return axisLabels; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} + +public enum SpectrogramWindowSize implements IndexingInterface { + ONE_MINUTE (0, 1f, "1 Min.", new float[]{1, .5, 0}, 1000), + ONE_MINUTE_THIRTY (1, 1.5f, "1.5 Min.", new float[]{1.5, 1, .5, 0}, 50), + THREE_MINUTES (2, 3f, "3 Min.", new float[]{3, 2, 1, 0}, 100), + SIX_MINUTES (3, 6f, "6 Min.", new float[]{6, 5, 4, 3, 2, 1, 0}, 200), + THIRTY_MINUTES (4, 30f, "30 Min.", new float[]{30, 25, 20, 15, 10, 5, 0}, 1000); + + private final int index; + private final float value; + private final String label; + private final float[] axisLabels; + private final int scrollSpeed; + + SpectrogramWindowSize(int index, float value, String label, float[] axisLabels, int scrollSpeed) { + this.index = index; + this.value = value; + this.label = label; + this.axisLabels = axisLabels; + this.scrollSpeed = scrollSpeed; + } + + public float getValue() { + return value; + } + + public float[] getAxisLabels() { + return axisLabels; + } + + public int getScrollSpeed() { + return scrollSpeed; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values()) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index 52c5c4df9..e899c4b47 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -39,30 +39,13 @@ class W_Spectrogram extends Widget { int paddingRight = 26; int paddingTop = 8; int paddingBottom = 50; - int numHorizAxisDivs = 2; // == 40Hz - int numVertAxisDivs = 8; - final int[][] vertAxisLabels = { - {20, 15, 10, 5, 0, 5, 10, 15, 20}, - {40, 30, 20, 10, 0, 10, 20, 30, 40}, - {60, 45, 30, 15, 0, 15, 30, 45, 60}, - {100, 75, 50, 25, 0, 25, 50, 75, 100}, - {120, 90, 60, 30, 0, 30, 60, 90, 120}, - {250, 188, 125, 63, 0, 63, 125, 188, 250} - }; - int[] vertAxisLabel; - final float[][] horizAxisLabels = { - {30, 25, 20, 15, 10, 5, 0}, - {6, 5, 4, 3, 2, 1, 0}, - {3, 2, 1, 0}, - {1.5, 1, .5, 0}, - {1, .5, 0} - }; - float[] horizAxisLabel; - StringList horizAxisLabelStrings; + StringList horizontalAxisLabelStrings; float[] topFFTAvg; float[] botFFTAvg; + private SpectrogramMaxFrequency maxFrequency = SpectrogramMaxFrequency.MAX_60; + private SpectrogramWindowSize windowSize = SpectrogramWindowSize.ONE_MINUTE; private FFTLogLin logLin = FFTLogLin.LIN; W_Spectrogram(PApplet _parent) { @@ -84,25 +67,18 @@ class W_Spectrogram extends Widget { graphW = w - paddingRight - paddingLeft; graphH = h - paddingBottom - paddingTop; - //FIX ME REMOVE THESE - settings.spectMaxFrqSave = 2; - settings.spectSampleRateSave = 4; - - vertAxisLabel = vertAxisLabels[settings.spectMaxFrqSave]; - horizAxisLabel = horizAxisLabels[settings.spectSampleRateSave]; - horizAxisLabelStrings = new StringList(); //Fetch/calculate the time strings for the horizontal axis ticks - fetchTimeStrings(numHorizAxisDivs); + horizontalAxisLabelStrings = fetchTimeStrings(); //This is the protocol for setting up dropdowns. //Note that these 3 dropdowns correspond to the 3 global functions below //You just need to make sure the "id" (the 1st String) has the same name as the corresponding function - addDropdown("SpectrogramMaxFreq", "Max Freq", Arrays.asList(settings.spectMaxFrqArray), settings.spectMaxFrqSave); - addDropdown("SpectrogramSampleRate", "Window", Arrays.asList(settings.spectSampleRateArray), settings.spectSampleRateSave); - addDropdown("SpectrogramLogLin", "Log/Lin", logLin.getEnumStringsAsList(), logLin.getIndex()); + addDropdown("spectrogramMaxFrequencyDropdown", "Max Hz", maxFrequency.getEnumStringsAsList(), maxFrequency.getIndex()); + addDropdown("spectrogramSampleRateDropdown", "Window", windowSize.getEnumStringsAsList(), windowSize.getIndex()); + addDropdown("spectrogramLogLinDropdown", "Log/Lin", logLin.getEnumStringsAsList(), logLin.getIndex()); //Resize the height of the data image using default - dataImageH = vertAxisLabel[0] * 2; + dataImageH = maxFrequency.getAxisLabels()[0] * 2; //Create image using correct dimensions! Fixes bug where image size and labels do not align on session start. dataImg = createImage(dataImageW, dataImageH, RGB); } @@ -133,7 +109,8 @@ class W_Spectrogram extends Widget { //Make sure we are always draw new pixels on the right xPos = dataImg.width - 1; //Fetch/calculate the time strings for the horizontal axis ticks - fetchTimeStrings(numHorizAxisDivs); + horizontalAxisLabelStrings.clear(); + horizontalAxisLabelStrings = fetchTimeStrings(); } //State change check @@ -281,17 +258,18 @@ class W_Spectrogram extends Widget { pushStyle(); //draw horizontal axis ticks from left to right int tickMarkSize = 7; //in pixels - float horizAxisX = graphX; - float horizAxisY = graphY + scaledH * dataImageH; + float horizontalAxisX = graphX; + float horizontalAxisY = graphY + scaledH * dataImageH; stroke(255); fill(255); strokeWeight(2); textSize(11); - for (int i = 0; i <= numHorizAxisDivs; i++) { - float offset = scaledW * dataImageW * (float(i) / numHorizAxisDivs); - line(horizAxisX + offset, horizAxisY, horizAxisX + offset, horizAxisY + tickMarkSize); - if (horizAxisLabelStrings.get(i) != null) { - text(horizAxisLabelStrings.get(i), horizAxisX + offset - (int)textWidth(horizAxisLabelStrings.get(i))/2, horizAxisY + tickMarkSize * 3); + int horizontalAxisDivCount = windowSize.getAxisLabels().length; + for (int i = 0; i < horizontalAxisDivCount; i++) { + float offset = scaledW * dataImageW * (float(i) / horizontalAxisDivCount); + line(horizontalAxisX + offset, horizontalAxisY, horizontalAxisX + offset, horizontalAxisY + tickMarkSize); + if (horizontalAxisLabelStrings.get(i) != null) { + text(horizontalAxisLabelStrings.get(i), horizontalAxisX + offset - (int)textWidth(horizontalAxisLabelStrings.get(i))/2, horizontalAxisY + tickMarkSize * 3); } } popStyle(); @@ -312,19 +290,20 @@ class W_Spectrogram extends Widget { pushStyle(); //draw vertical axis ticks from top to bottom - float vertAxisX = graphX; - float vertAxisY = graphY; + float verticalAxisX = graphX; + float verticalAxisY = graphY; stroke(255); fill(255); textSize(12); strokeWeight(2); - for (int i = 0; i <= numVertAxisDivs; i++) { - float offset = scaledH * dataImageH * (float(i) / numVertAxisDivs); - //if (i <= numVertAxisDivs/2) offset -= 2; - line(vertAxisX, vertAxisY + offset, vertAxisX - tickMarkSize, vertAxisY + offset); - if (vertAxisLabel[i] == 0) midLineY = int(vertAxisY + offset); + int verticalAxisDivCount = maxFrequency.getAxisLabels().length - 1; + for (int i = 0; i < verticalAxisDivCount; i++) { + float offset = scaledH * dataImageH * (float(i) / verticalAxisDivCount); + //if (i <= verticalAxisDivCount/2) offset -= 2; + line(verticalAxisX, verticalAxisY + offset, verticalAxisX - tickMarkSize, verticalAxisY + offset); + if (maxFrequency.getAxisLabels()[i] == 0) midLineY = int(verticalAxisY + offset); offset += paddingTop/2; - text(vertAxisLabel[i], vertAxisX - tickMarkSize*2 - textWidth(Integer.toString(vertAxisLabel[i])), vertAxisY + offset); + text(maxFrequency.getAxisLabels()[i], verticalAxisX - tickMarkSize*2 - textWidth(Integer.toString(maxFrequency.getAxisLabels()[i])), verticalAxisY + offset); } popStyle(); @@ -415,23 +394,22 @@ class W_Spectrogram extends Widget { return sum / _activeChan.size(); } - void fetchTimeStrings(int numAxisTicks) { - horizAxisLabelStrings.clear(); + private StringList fetchTimeStrings() { + StringList output = new StringList(); LocalDateTime time; DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); - if (getCurrentTimeStamp() == 0) { time = LocalDateTime.now(); } else { time = LocalDateTime.ofInstant(Instant.ofEpochMilli(getCurrentTimeStamp()), TimeZone.getDefault().toZoneId()); } - - for (int i = 0; i <= numAxisTicks; i++) { - long l = (long)(horizAxisLabel[i] * 60f); + for (int i = 0; i < windowSize.getAxisLabels().length; i++) { + long l = (long)(windowSize.getAxisLabels()[i] * 60f); LocalDateTime t = time.minus(l, ChronoUnit.SECONDS); - horizAxisLabelStrings.append(t.format(formatter)); + output.append(t.format(formatter)); } + return output; } //Identical to the method in TimeSeries, but allows spectrogram to get the data directly from the playback data in the background @@ -457,45 +435,33 @@ class W_Spectrogram extends Widget { public void setLogLin(int n) { logLin = logLin.values()[n]; } + + public void setMaxFrequency(int n) { + maxFrequency = maxFrequency.values()[n]; + // Resize the height of the data image + dataImageH = maxFrequency.getAxisLabels()[0] * 2; + // Overwrite the existing image + dataImg = createImage(dataImageW, dataImageH, RGB); + } + + public void setWindowSize(int n) { + windowSize = windowSize.values()[n]; + setScrollSpeed(windowSize.getScrollSpeed()); + horizontalAxisLabelStrings.clear(); + horizontalAxisLabelStrings = fetchTimeStrings(); + dataImg = createImage(dataImageW, dataImageH, RGB); + + } }; -//These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected -//triggered when there is an event in the Spectrogram Widget MaxFreq. Dropdown -void SpectrogramMaxFreq(int n) { - settings.spectMaxFrqSave = n; - //reset the vertical axis labels - w_spectrogram.vertAxisLabel = w_spectrogram.vertAxisLabels[n]; - //Resize the height of the data image - w_spectrogram.dataImageH = w_spectrogram.vertAxisLabel[0] * 2; - //overwrite the existing image because the sample rate is about to change - w_spectrogram.dataImg = createImage(w_spectrogram.dataImageW, w_spectrogram.dataImageH, RGB); +public void spectrogramMaxFrequencyDropdown(int n) { + w_spectrogram.setMaxFrequency(n); } -void SpectrogramSampleRate(int n) { - settings.spectSampleRateSave = n; - //overwrite the existing image because the sample rate is about to change - w_spectrogram.dataImg = createImage(w_spectrogram.dataImageW, w_spectrogram.dataImageH, RGB); - w_spectrogram.horizAxisLabel = w_spectrogram.horizAxisLabels[n]; - if (n == 0) { - w_spectrogram.numHorizAxisDivs = 6; - w_spectrogram.setScrollSpeed(1000); - } else if (n == 1) { - w_spectrogram.numHorizAxisDivs = 6; - w_spectrogram.setScrollSpeed(200); - } else if (n == 2) { - w_spectrogram.numHorizAxisDivs = 3; - w_spectrogram.setScrollSpeed(100); - } else if (n == 3) { - w_spectrogram.numHorizAxisDivs = 3; - w_spectrogram.setScrollSpeed(50); - } else if (n == 4) { - w_spectrogram.numHorizAxisDivs = 2; - w_spectrogram.setScrollSpeed(25); - } - w_spectrogram.horizAxisLabelStrings.clear(); - w_spectrogram.fetchTimeStrings(w_spectrogram.numHorizAxisDivs); +public void spectrogramWindowDropdown(int n) { + w_spectrogram.setWindowSize(n); } -public void SpectrogramLogLin(int n) { +public void spectrogramLogLinDropdown(int n) { w_spectrogram.setLogLin(n); } \ No newline at end of file From 19a14e9b792da2dce8543420bb9aba7dc9a367dd Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 10 Apr 2025 14:30:54 -0500 Subject: [PATCH 04/29] Refactor settings for TimeSeries, Accelerometer, Analog Read, and Digital Read widgets --- OpenBCI_GUI/AccelerometerEnums.pde | 102 +++++++++++++++++ OpenBCI_GUI/AnalogReadEnums.pde | 85 ++++++++++++++ OpenBCI_GUI/SessionSettings.pde | 55 ++++----- OpenBCI_GUI/TimeSeriesEnums.pde | 127 +++++++++++++++++++++ OpenBCI_GUI/W_Accelerometer.pde | 101 +++++++---------- OpenBCI_GUI/W_AnalogRead.pde | 159 ++++++++++---------------- OpenBCI_GUI/W_DigitalRead.pde | 54 ++++----- OpenBCI_GUI/W_TimeSeries.pde | 176 +++-------------------------- 8 files changed, 483 insertions(+), 376 deletions(-) create mode 100644 OpenBCI_GUI/AccelerometerEnums.pde create mode 100644 OpenBCI_GUI/AnalogReadEnums.pde create mode 100644 OpenBCI_GUI/TimeSeriesEnums.pde diff --git a/OpenBCI_GUI/AccelerometerEnums.pde b/OpenBCI_GUI/AccelerometerEnums.pde new file mode 100644 index 000000000..15a4a3086 --- /dev/null +++ b/OpenBCI_GUI/AccelerometerEnums.pde @@ -0,0 +1,102 @@ +public enum AccelerometerVerticalScale implements IndexingInterface +{ + AUTO (0, 0, "Auto"), + ONE_G (1, 1, "1 g"), + TWO_G (2, 2, "2 g"), + FOUR_G (3, 4, "4 g"); + + private int index; + private int value; + private String label; + private static AccelerometerVerticalScale[] values = AccelerometerVerticalScale.values(); + + AccelerometerVerticalScale(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } + + public int getHighestValue() { + int highestValue = 0; + for (AccelerometerVerticalScale scale : values) { + if (scale.getValue() > highestValue) { + highestValue = scale.getValue(); + } + } + return highestValue; + } +} + +public enum AccelerometerHorizontalScale implements IndexingInterface +{ + ONE_SEC (1, 1, "1 sec"), + THREE_SEC (2, 3, "3 sec"), + FIVE_SEC (3, 5, "5 sec"), + TEN_SEC (4, 10, "10 sec"), + TWENTY_SEC (5, 20, "20 sec"); + + private int index; + private int value; + private String label; + private static AccelerometerHorizontalScale[] values = AccelerometerHorizontalScale.values(); + + AccelerometerHorizontalScale(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } + + public int getHighestValue() { + int highestValue = 0; + for (AccelerometerHorizontalScale scale : values) { + if (scale.getValue() > highestValue) { + highestValue = scale.getValue(); + } + } + return highestValue; + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/AnalogReadEnums.pde b/OpenBCI_GUI/AnalogReadEnums.pde new file mode 100644 index 000000000..c56040543 --- /dev/null +++ b/OpenBCI_GUI/AnalogReadEnums.pde @@ -0,0 +1,85 @@ +public enum AnalogReadVerticalScale implements IndexingInterface +{ + AUTO (0, 0, "Auto"), + FIFTY (1, 50, "50 uV"), + ONE_HUNDRED (2, 100, "100 uV"), + TWO_HUNDRED (3, 200, "200 uV"), + FOUR_HUNDRED (4, 400, "400 uV"), + ONE_THOUSAND_FIFTY (5, 1050, "1050 uV"), + TEN_THOUSAND (6, 10000, "10000 uV"); + + private int index; + private int value; + private String label; + private static AnalogReadVerticalScale[] values = AnalogReadVerticalScale.values(); + + AnalogReadVerticalScale(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} + +public enum AnalogReadHorizontalScale implements IndexingInterface +{ + ONE_SEC (1, 1, "1 sec"), + THREE_SEC (2, 3, "3 sec"), + FIVE_SEC (3, 5, "5 sec"), + TEN_SEC (4, 10, "10 sec"), + TWENTY_SEC (5, 20, "20 sec"); + + private int index; + private int value; + private String label; + private static AnalogReadHorizontalScale[] values = AnalogReadHorizontalScale.values(); + + AnalogReadHorizontalScale(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList<>(); + for (IndexingInterface enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde index 76a3879e8..acfc8a7f0 100644 --- a/OpenBCI_GUI/SessionSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -56,14 +56,6 @@ class SessionSettings { private long logFileMaxDurationNano = -1; //this is a global CColor that determines the style of all widget dropdowns ... this should go in WidgetManager.pde CColor dropdownColors = new CColor(); - ///These `Save` vars are set to default when each widget instantiates - ///and updated every time user selects from dropdown - //Accelerometer settings - int accVertScaleSave; - int accHorizScaleSave; - //Analog Read settings - int arVertScaleSave; - int arHorizScaleSave; //default configuration settings file location and file name variables private String sessionPath = ""; @@ -86,14 +78,6 @@ class SessionSettings { "SynthSixteenDefaultSettings.json" }; - //Used to set text in dropdown menus when loading Accelerometer settings - String[] accVertScaleArray = {"Auto","1 g", "2 g"}; - String[] accHorizScaleArray = {"Sync", "1 sec", "3 sec", "5 sec", "10 sec", "20 sec"}; - - //Used to set text in dropdown menus when loading Analog Read settings - String[] arVertScaleArray = {"Auto", "50", "100", "200", "400", "1000", "10000"}; - String[] arHorizScaleArray = {"Sync", "1 sec", "3 sec", "5 sec", "10 sec", "20 sec"}; - //Load Accel. dropdown variables int loadAccelVertScale; int loadAccelHorizScale; @@ -278,9 +262,9 @@ class SessionSettings { //Make a new JSON Object for Time Series Settings JSONObject saveTSSettings = new JSONObject(); - saveTSSettings.setInt("Time Series Vert Scale", w_timeSeries.getTSVertScale().getIndex()); - saveTSSettings.setInt("Time Series Horiz Scale", w_timeSeries.getTSHorizScale().getIndex()); - saveTSSettings.setInt("Time Series Label Mode", w_timeSeries.getTSLabelMode().getIndex()); + saveTSSettings.setInt("Time Series Vert Scale", w_timeSeries.getVerticalScale().getIndex()); + saveTSSettings.setInt("Time Series Horiz Scale", w_timeSeries.getHorizontalScale().getIndex()); + saveTSSettings.setInt("Time Series Label Mode", w_timeSeries.getLabelMode().getIndex()); //Save data from the Active channel checkBoxes JSONArray saveActiveChanTS = new JSONArray(); int numActiveTSChan = w_timeSeries.tsChanSelect.getActiveChannels().size(); @@ -294,8 +278,11 @@ class SessionSettings { //Make a second JSON object within our JSONArray to store Global settings for the GUI JSONObject saveGlobalSettings = new JSONObject(); saveGlobalSettings.setInt("Current Layout", currentLayout); + //FIX ME + /* saveGlobalSettings.setInt("Analog Read Vert Scale", arVertScaleSave); saveGlobalSettings.setInt("Analog Read Horiz Scale", arHorizScaleSave); + */ if (currentBoard instanceof SmoothingCapableBoard) { saveGlobalSettings.setBoolean("Data Smoothing", ((SmoothingCapableBoard)currentBoard).getSmoothingActive()); } @@ -329,12 +316,15 @@ class SessionSettings { saveSettingsJSONData.setJSONObject(kJSONKeyFFT, saveFFTSettings); //next object will be set to sessionSettingsChannelCount+3, etc. ///////////////////////////////////////////////Setup new JSON object to save Accelerometer settings + //FIX ME + /* if (w_accelerometer != null) { JSONObject saveAccSettings = new JSONObject(); saveAccSettings.setInt("Accelerometer Vert Scale", accVertScaleSave); saveAccSettings.setInt("Accelerometer Horiz Scale", accHorizScaleSave); saveSettingsJSONData.setJSONObject(kJSONKeyAccel, saveAccSettings); } + */ ///////////////////////////////////////////////Save Networking settings String nwSettingsValues = dataProcessing.networkingSettings.getJson(); @@ -512,8 +502,11 @@ class SessionSettings { JSONObject loadGlobalSettings = loadSettingsJSONData.getJSONObject(kJSONKeySettings); //Store loaded layout to current layout variable currentLayout = loadGlobalSettings.getInt("Current Layout"); + //FIX ME + /* loadAnalogReadVertScale = loadGlobalSettings.getInt("Analog Read Vert Scale"); loadAnalogReadHorizScale = loadGlobalSettings.getInt("Analog Read Horiz Scale"); + */ //Load more global settings after this line, if needed Boolean loadDataSmoothingSetting = (currentBoard instanceof SmoothingCapableBoard) ? loadGlobalSettings.getBoolean("Data Smoothing") : null; @@ -528,12 +521,15 @@ class SessionSettings { fftFilterLoad = loadFFTSettings.getInt("FFT_Filter"); */ + //FIX ME + /* //get the Accelerometer settings if (w_accelerometer != null) { JSONObject loadAccSettings = loadSettingsJSONData.getJSONObject(kJSONKeyAccel); loadAccelVertScale = loadAccSettings.getInt("Accelerometer Vert Scale"); loadAccelHorizScale = loadAccSettings.getInt("Accelerometer Horiz Scale"); } + */ //get the Networking Settings loadNetworkingSettings = loadSettingsJSONData.getJSONObject(kJSONKeyNetworking); @@ -710,7 +706,9 @@ class SessionSettings { w_fft.cp5_widget.getController("UnfiltFilt").getCaptionLabel().setText(fftFilterArray[fftFilterLoad]); */ - ////////Apply Accelerometer settings; + ////////Apply Accelerometer settings + //FIX ME + /* if (w_accelerometer != null) { accelVertScale(loadAccelVertScale); w_accelerometer.cp5_widget.getController("accelVertScale").getCaptionLabel().setText(accVertScaleArray[loadAccelVertScale]); @@ -718,16 +716,19 @@ class SessionSettings { accelDuration(loadAccelHorizScale); w_accelerometer.cp5_widget.getController("accelDuration").getCaptionLabel().setText(accHorizScaleArray[loadAccelHorizScale]); } + */ ////////Apply Anolog Read dropdowns to Live Cyton Only + //FIX ME + /* if (eegDataSource == DATASOURCE_CYTON) { - ////////Apply Analog Read settings VertScale_AR(loadAnalogReadVertScale); w_analogRead.cp5_widget.getController("VertScale_AR").getCaptionLabel().setText(arVertScaleArray[loadAnalogReadVertScale]); Duration_AR(loadAnalogReadHorizScale); w_analogRead.cp5_widget.getController("Duration_AR").getCaptionLabel().setText(arHorizScaleArray[loadAnalogReadHorizScale]); } + */ ////////////////////////////Apply Headplot settings //FIX ME @@ -854,14 +855,14 @@ class SessionSettings { JSONObject loadTimeSeriesSettings = loadSettingsJSONData.getJSONObject(kJSONKeyTimeSeries); ////////Apply Time Series widget settings - w_timeSeries.setTSVertScale(loadTimeSeriesSettings.getInt("Time Series Vert Scale")); - w_timeSeries.cp5_widget.getController("VertScale_TS").getCaptionLabel().setText(w_timeSeries.getTSVertScale().getString()); //changes front-end + w_timeSeries.setVerticalScale(loadTimeSeriesSettings.getInt("Time Series Vert Scale")); + w_timeSeries.cp5_widget.getController("VertScale_TS").getCaptionLabel().setText(w_timeSeries.getVerticalScale().getString()); //changes front-end - w_timeSeries.setTSHorizScale(loadTimeSeriesSettings.getInt("Time Series Horiz Scale")); - w_timeSeries.cp5_widget.getController("Duration").getCaptionLabel().setText(w_timeSeries.getTSHorizScale().getString()); + w_timeSeries.setHorizontalScale(loadTimeSeriesSettings.getInt("Time Series Horiz Scale")); + w_timeSeries.cp5_widget.getController("Duration").getCaptionLabel().setText(w_timeSeries.getHorizontalScale().getString()); - w_timeSeries.setTSLabelMode(loadTimeSeriesSettings.getInt("Time Series Label Mode")); - w_timeSeries.cp5_widget.getController("LabelMode_TS").getCaptionLabel().setText(w_timeSeries.getTSLabelMode().getString()); + w_timeSeries.setLabelMode(loadTimeSeriesSettings.getInt("Time Series Label Mode")); + w_timeSeries.cp5_widget.getController("LabelMode_TS").getCaptionLabel().setText(w_timeSeries.getLabelMode().getString()); JSONArray loadTSChan = loadTimeSeriesSettings.getJSONArray("activeChannels"); w_timeSeries.tsChanSelect.deactivateAllButtons(); diff --git a/OpenBCI_GUI/TimeSeriesEnums.pde b/OpenBCI_GUI/TimeSeriesEnums.pde new file mode 100644 index 000000000..04fa7f3e5 --- /dev/null +++ b/OpenBCI_GUI/TimeSeriesEnums.pde @@ -0,0 +1,127 @@ +public enum TimeSeriesXLim implements IndexingInterface +{ + ONE (0, 1, "1 sec"), + THREE (1, 3, "3 sec"), + FIVE (2, 5, "5 sec"), + TEN (3, 10, "10 sec"), + TWENTY (4, 20, "20 sec"); + + private int index; + private int value; + private String label; + private static TimeSeriesXLim[] vals = values(); + + TimeSeriesXLim(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList(); + for (IndexingInterface val : vals) { + enumStrings.add(val.getString()); + } + return enumStrings; + } +} + +public enum TimeSeriesYLim implements IndexingInterface +{ + AUTO (0, 0, "Auto"), + UV_10(1, 10, "10 uV"), + UV_25(2, 25, "25 uV"), + UV_50 (3, 50, "50 uV"), + UV_100 (4, 100, "100 uV"), + UV_200 (5, 200, "200 uV"), + UV_400 (6, 400, "400 uV"), + UV_1000 (7, 1000, "1000 uV"), + UV_10000 (8, 10000, "10000 uV"); + + private int index; + private int value; + private String label; + private static TimeSeriesYLim[] vals = values(); + + TimeSeriesYLim(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList(); + for (IndexingInterface val : vals) { + enumStrings.add(val.getString()); + } + return enumStrings; + } +} + +public enum TimeSeriesLabelMode implements IndexingInterface +{ + OFF (0, 0, "Off"), + MINIMAL (1, 1, "Minimal"), + ON (2, 2, "On"); + + private int index; + private int value; + private String label; + private static TimeSeriesLabelMode[] vals = values(); + + TimeSeriesLabelMode(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public static List getEnumStringsAsList() { + List enumStrings = new ArrayList(); + for (IndexingInterface val : vals) { + enumStrings.add(val.getString()); + } + return enumStrings; + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/W_Accelerometer.pde b/OpenBCI_GUI/W_Accelerometer.pde index 46b444eeb..57b6bbd81 100644 --- a/OpenBCI_GUI/W_Accelerometer.pde +++ b/OpenBCI_GUI/W_Accelerometer.pde @@ -19,21 +19,17 @@ class W_Accelerometer extends Widget { color strokeColor = color(138, 146, 153); color eggshell = color(255, 253, 248); - //Graphing variables - int[] xLimOptions = {0, 1, 3, 5, 10, 20}; //number of seconds (x axis of graph) - int[] yLimOptions = {0, 1, 2}; - float accelXyzLimit = 4.0; //hard limit on all accel values - int accelHorizLimit = 20; - float[] lastAccelVals; - AccelerometerBar accelerometerBar; - //Bottom xyz graph + AccelerometerBar accelerometerBar; int accelGraphWidth; int accelGraphHeight; int accelGraphX; int accelGraphY; int accPadding = 30; final int PAD_FIVE = 5; + private AccelerometerVerticalScale verticalScale = AccelerometerVerticalScale.AUTO; + private AccelerometerHorizontalScale horizontalScale = AccelerometerHorizontalScale.FIVE_SEC; + float[] lastDataSampleValues; //Circular 3d xyz graph float polarWindowX; @@ -41,8 +37,7 @@ class W_Accelerometer extends Widget { int polarWindowWidth; int polarWindowHeight; float polarCorner; - - float yMaxMin; + private float polarYMaxMin; boolean accelInitHasOccured = false; private Button accelModeButton; @@ -54,24 +49,20 @@ class W_Accelerometer extends Widget { accelBoard = (AccelerometerCapableBoard)currentBoard; - //Default dropdown settings - settings.accVertScaleSave = 0; - settings.accHorizScaleSave = 3; - //Make dropdowns - addDropdown("accelVertScale", "Vert Scale", Arrays.asList(settings.accVertScaleArray), settings.accVertScaleSave); - addDropdown("accelDuration", "Window", Arrays.asList(settings.accHorizScaleArray), settings.accHorizScaleSave); + addDropdown("accelerometerVerticalScaleDropdown", "Vert Scale", verticalScale.getEnumStringsAsList(), verticalScale.getIndex()); + addDropdown("accelerometerHorizontalScaleDropdown", "Window", horizontalScale.getEnumStringsAsList(), horizontalScale.getIndex()); setGraphDimensions(); - yMaxMin = adjustYMaxMinBasedOnSource(); + polarYMaxMin = adjustYMaxMinBasedOnSource(); //XYZ buffer for bottom graph - lastAccelVals = new float[NUM_ACCEL_DIMS]; + lastDataSampleValues = new float[NUM_ACCEL_DIMS]; - //create our channel bar and populate our accelerometerBar array! - accelerometerBar = new AccelerometerBar(_parent, accelXyzLimit, accelGraphX, accelGraphY, accelGraphWidth, accelGraphHeight); - accelerometerBar.adjustTimeAxis(xLimOptions[settings.accHorizScaleSave]); - accelerometerBar.adjustVertScale(yLimOptions[settings.accVertScaleSave]); + //Create our channel bar and populate our accelerometerBar array + accelerometerBar = new AccelerometerBar(_parent, verticalScale.getHighestValue(), accelGraphX, accelGraphY, accelGraphWidth, accelGraphHeight); + accelerometerBar.adjustTimeAxis(horizontalScale.getValue()); + accelerometerBar.adjustVertScale(verticalScale.getValue()); createAccelModeButton("accelModeButton", "Turn Accel. Off", (int)(x + 1), (int)(y0 + navHeight + 1), 120, navHeight - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); } @@ -80,19 +71,14 @@ class W_Accelerometer extends Widget { float _yMaxMin; if (eegDataSource == DATASOURCE_CYTON) { _yMaxMin = 4.0; - }else if (eegDataSource == DATASOURCE_GANGLION || globalChannelCount == 4) { + } else if (eegDataSource == DATASOURCE_GANGLION || globalChannelCount == 4) { _yMaxMin = 2.0; - accelXyzLimit = 2.0; - }else{ + } else { _yMaxMin = 4.0; } return _yMaxMin; } - int nPointsBasedOnDataSource() { - return accelHorizLimit * ((AccelerometerCapableBoard)currentBoard).getAccelSampleRate(); - } - void update() { super.update(); //calls the parent update() method of Widget (DON'T REMOVE) @@ -101,7 +87,7 @@ class W_Accelerometer extends Widget { accelerometerBar.update(); //update the current Accelerometer values - lastAccelVals = accelerometerBar.getLastAccelVals(); + lastDataSampleValues = accelerometerBar.getLastAccelVals(); } //ignore top left button interaction when widgetSelector dropdown is active @@ -117,7 +103,7 @@ class W_Accelerometer extends Widget { } public float getLastAccelVal(int val) { - return lastAccelVals[val]; + return lastDataSampleValues[val]; } void draw() { @@ -230,9 +216,9 @@ class W_Accelerometer extends Widget { //Draw the current accelerometer values as text void drawAccValues() { - float displayX = (float)lastAccelVals[0]; - float displayY = (float)lastAccelVals[1]; - float displayZ = (float)lastAccelVals[2]; + float displayX = (float)lastDataSampleValues[0]; + float displayY = (float)lastDataSampleValues[1]; + float displayZ = (float)lastDataSampleValues[2]; textAlign(LEFT,CENTER); textFont(h1,20); fill(ACCEL_X_COLOR); @@ -245,18 +231,18 @@ class W_Accelerometer extends Widget { //Draw the current accelerometer values as a 3D graph void draw3DGraph() { - float displayX = (float)lastAccelVals[0]; - float displayY = (float)lastAccelVals[1]; - float displayZ = (float)lastAccelVals[2]; + float displayX = (float)lastDataSampleValues[0]; + float displayY = (float)lastDataSampleValues[1]; + float displayZ = (float)lastDataSampleValues[2]; noFill(); strokeWeight(3); stroke(ACCEL_X_COLOR); - line(polarWindowX, polarWindowY, polarWindowX+map(displayX, -yMaxMin, yMaxMin, -polarWindowWidth/2, polarWindowWidth/2), polarWindowY); + line(polarWindowX, polarWindowY, polarWindowX+map(displayX, -polarYMaxMin, polarYMaxMin, -polarWindowWidth/2, polarWindowWidth/2), polarWindowY); stroke(ACCEL_Y_COLOR); - line(polarWindowX, polarWindowY, polarWindowX+map((sqrt(2)*displayY/2), -yMaxMin, yMaxMin, -polarWindowWidth/2, polarWindowWidth/2), polarWindowY+map((sqrt(2)*displayY/2), -yMaxMin, yMaxMin, polarWindowWidth/2, -polarWindowWidth/2)); + line(polarWindowX, polarWindowY, polarWindowX+map((sqrt(2)*displayY/2), -polarYMaxMin, polarYMaxMin, -polarWindowWidth/2, polarWindowWidth/2), polarWindowY+map((sqrt(2)*displayY/2), -polarYMaxMin, polarYMaxMin, polarWindowWidth/2, -polarWindowWidth/2)); stroke(ACCEL_Z_COLOR); - line(polarWindowX, polarWindowY, polarWindowX, polarWindowY+map(displayZ, -yMaxMin, yMaxMin, polarWindowWidth/2, -polarWindowWidth/2)); + line(polarWindowX, polarWindowY, polarWindowX, polarWindowY+map(displayZ, -polarYMaxMin, polarYMaxMin, polarWindowWidth/2, -polarWindowWidth/2)); strokeWeight(1); } @@ -277,25 +263,24 @@ class W_Accelerometer extends Widget { } } -};//end W_Accelerometer class + public void setVerticalScale(int n) { + verticalScale = AccelerometerVerticalScale.values()[n]; + accelerometerBar.adjustVertScale(verticalScale.getValue()); + } -//These functions are activated when an item from the corresponding dropdown is selected -void accelVertScale(int n) { - settings.accVertScaleSave = n; - w_accelerometer.accelerometerBar.adjustVertScale(w_accelerometer.yLimOptions[n]); -} + public void setHorizontalScale(int n) { + horizontalScale = AccelerometerHorizontalScale.values()[n]; + accelerometerBar.adjustTimeAxis(horizontalScale.getValue()); + } -//triggered when there is an event in the Duration Dropdown -void accelDuration(int n) { - settings.accHorizScaleSave = n; +}; - //Sync the duration of Time Series, Accelerometer, and Analog Read(Cyton Only) - if (n == 0) { - w_accelerometer.accelerometerBar.adjustTimeAxis(w_timeSeries.getTSHorizScale().getValue()); - } else { - //set accelerometer x axis to the duration selected from dropdown - w_accelerometer.accelerometerBar.adjustTimeAxis(w_accelerometer.xLimOptions[n]); - } +public void accelerometerVerticalScaleDropdown(int n) { + w_accelerometer.setVerticalScale(n); +} + +public void accelerometerHorizontalScaleDropdown(int n) { + w_accelerometer.setHorizontalScale(n); } //======================================================================================================================== @@ -329,7 +314,7 @@ class AccelerometerBar { private AccelerometerCapableBoard accelBoard; - AccelerometerBar(PApplet _parent, float accelXyzLimit, int _x, int _y, int _w, int _h) { //channel number, x/y location, height, width + AccelerometerBar(PApplet _parent, float _yLimit, int _x, int _y, int _w, int _h) { //channel number, x/y location, height, width // This widget is only instantiated when the board is accel capable, so we don't need to check accelBoard = (AccelerometerCapableBoard)currentBoard; @@ -345,7 +330,7 @@ class AccelerometerBar { plot.setMar(0f, 0f, 0f, 0f); plot.setLineColor((int)channelColors[(NUM_ACCEL_DIMS)%8]); plot.setXLim(-numSeconds,0); //set the horizontal scale - plot.setYLim(-accelXyzLimit, accelXyzLimit); //change this to adjust vertical scale + plot.setYLim(-_yLimit, _yLimit); //change this to adjust vertical scale //plot.setPointSize(2); plot.setPointColor(0); plot.getXAxis().setAxisLabelText("Time (s)"); diff --git a/OpenBCI_GUI/W_AnalogRead.pde b/OpenBCI_GUI/W_AnalogRead.pde index aa5ce88ee..c06d991df 100644 --- a/OpenBCI_GUI/W_AnalogRead.pde +++ b/OpenBCI_GUI/W_AnalogRead.pde @@ -1,93 +1,65 @@ - -//////////////////////////////////////////////////// -// -// W_AnalogRead is used to visiualze analog voltage values -// -// Created: AJ Keller -// -// -///////////////////////////////////////////////////, +//////////////////////////////////////////////////////////////////////// +// // +// W_AnalogRead is used to visualize analog voltage values // +// // +// Created: AJ Keller // +// Refactored: Richard Waltman, April 2025 // +// // +// // +//////////////////////////////////////////////////////////////////////// class W_AnalogRead extends Widget { - //to see all core variables/methods of the Widget class, refer to Widget.pde - //put your custom variables here... - - private int numAnalogReadBars; - float xF, yF, wF, hF; - float arPadding; - float ar_x, ar_y, ar_h, ar_w; // values for actual time series chart (rectangle encompassing all analogReadBars) - float plotBottomWell; - float playbackWidgetHeight; - int analogReadBarHeight; - - AnalogReadBar[] analogReadBars; + private float arPadding; + // values for actual time series chart (rectangle encompassing all analogReadBars) + private float ar_x, ar_y, ar_h, ar_w; + private float plotBottomWell; + private float playbackWidgetHeight; + private int analogReadBarHeight; - int[] xLimOptions = {0, 1, 3, 5, 10, 20}; // number of seconds (x axis of graph) - int[] yLimOptions = {0, 50, 100, 200, 400, 1000, 10000}; // 0 = Autoscale ... everything else is uV + private final int NUM_ANALOG_READ_BARS = 3; + private AnalogReadBar[] analogReadBars; + private AnalogReadHorizontalScale horizontalScale = AnalogReadHorizontalScale.FIVE_SEC; + private AnalogReadVerticalScale verticalScale = AnalogReadVerticalScale.ONE_THOUSAND_FIFTY; private boolean allowSpillover = false; - //Initial dropdown settings - private int arInitialVertScaleIndex = 5; - private int arInitialHorizScaleIndex = 0; - private Button analogModeButton; private AnalogCapableBoard analogBoard; W_AnalogRead(PApplet _parent) { - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + super(_parent); analogBoard = (AnalogCapableBoard)currentBoard; - //Analog Read settings - settings.arVertScaleSave = 5; //updates in VertScale_AR() - settings.arHorizScaleSave = 0; //updates in Duration_AR() - - //This is the protocol for setting up dropdowns. - //Note that these 3 dropdowns correspond to the 3 global functions below - //You just need to make sure the "id" (the 1st String) has the same name as the corresponding function - addDropdown("VertScale_AR", "Vert Scale", Arrays.asList(settings.arVertScaleArray), arInitialVertScaleIndex); - addDropdown("Duration_AR", "Window", Arrays.asList(settings.arHorizScaleArray), arInitialHorizScaleIndex); - // addDropdown("Spillover", "Spillover", Arrays.asList("False", "True"), 0); - - //set number of analog reads - numAnalogReadBars = 3; - - xF = float(x); //float(int( ... is a shortcut for rounding the float down... so that it doesn't creep into the 1px margin - yF = float(y); - wF = float(w); - hF = float(h); + addDropdown("analogReadVerticalScaleDropdown", "Vert Scale", verticalScale.getEnumStringsAsList(), verticalScale.getIndex()); + addDropdown("analogReadHorizontalScaleDropdown", "Window", horizontalScale.getEnumStringsAsList(), horizontalScale.getIndex()); plotBottomWell = 45.0; //this appears to be an arbitrary vertical space adds GPlot leaves at bottom, I derived it through trial and error arPadding = 10.0; - ar_x = xF + arPadding; - ar_y = yF + (arPadding); - ar_w = wF - arPadding*2; - ar_h = hF - playbackWidgetHeight - plotBottomWell - (arPadding*2); - analogReadBarHeight = int(ar_h/numAnalogReadBars); + ar_x = float(x) + arPadding; + ar_y = float(y) + (arPadding); + ar_w = float(w) - arPadding*2; + ar_h = float(h) - playbackWidgetHeight - plotBottomWell - (arPadding*2); - analogReadBars = new AnalogReadBar[numAnalogReadBars]; + analogReadBars = new AnalogReadBar[NUM_ANALOG_READ_BARS]; + analogReadBarHeight = int(ar_h / analogReadBars.length); //create our channel bars and populate our analogReadBars array! - for(int i = 0; i < numAnalogReadBars; i++) { + for(int i = 0; i < analogReadBars.length; i++) { int analogReadBarY = int(ar_y) + i*(analogReadBarHeight); //iterate through bar locations AnalogReadBar tempBar = new AnalogReadBar(_parent, i+5, int(ar_x), analogReadBarY, int(ar_w), analogReadBarHeight); //int _channelNumber, int _x, int _y, int _w, int _h analogReadBars[i] = tempBar; - analogReadBars[i].adjustVertScale(yLimOptions[arInitialVertScaleIndex]); - //sync horiz axis to Time Series by default - analogReadBars[i].adjustTimeAxis(w_timeSeries.getTSHorizScale().getValue()); } - createAnalogModeButton("analogModeButton", "Turn Analog Read On", (int)(x0 + 1), (int)(y0 + navHeight + 1), 128, navHeight - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); - } + setVerticalScale(verticalScale.getIndex()); + setHorizontalScale(horizontalScale.getIndex()); - public int getNumAnalogReads() { - return numAnalogReadBars; + createAnalogModeButton("analogModeButton", "Turn Analog Read On", (int)(x0 + 1), (int)(y0 + navHeight + 1), 128, navHeight - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); } - void update() { + public void update() { super.update(); //calls the parent update() method of Widget (DON'T REMOVE) if (currentBoard instanceof DataSourcePlayback) { @@ -98,7 +70,7 @@ class W_AnalogRead extends Widget { } //update channel bars ... this means feeding new EEG data into plots - for(int i = 0; i < numAnalogReadBars; i++) { + for(int i = 0; i < analogReadBars.length; i++) { analogReadBars[i].update(); } @@ -114,32 +86,27 @@ class W_AnalogRead extends Widget { } } - void draw() { + public void draw() { super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) //remember to refer to x,y,w,h which are the positioning variables of the Widget class if (analogBoard.isAnalogActive()) { - for(int i = 0; i < numAnalogReadBars; i++) { + for(int i = 0; i < analogReadBars.length; i++) { analogReadBars[i].draw(); } } } - void screenResized() { + public void screenResized() { super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) - xF = float(x); //float(int( ... is a shortcut for rounding the float down... so that it doesn't creep into the 1px margin - yF = float(y); - wF = float(w); - hF = float(h); - - ar_x = xF + arPadding; - ar_y = yF + (arPadding); - ar_w = wF - arPadding*2; - ar_h = hF - playbackWidgetHeight - plotBottomWell - (arPadding*2); - analogReadBarHeight = int(ar_h/numAnalogReadBars); + ar_x = float(x) + arPadding; + ar_y = float(y) + (arPadding); + ar_w = float(w) - arPadding*2; + ar_h = float(h) - playbackWidgetHeight - plotBottomWell - (arPadding*2); + analogReadBarHeight = int(ar_h/analogReadBars.length); - for(int i = 0; i < numAnalogReadBars; i++) { + for(int i = 0; i < analogReadBars.length; i++) { int analogReadBarY = int(ar_y) + i*(analogReadBarHeight); //iterate through bar locations analogReadBars[i].screenResized(int(ar_x), analogReadBarY, int(ar_w), analogReadBarHeight); //bar x, bar y, bar w, bar h } @@ -147,11 +114,11 @@ class W_AnalogRead extends Widget { analogModeButton.setPosition((int)(x0 + 1), (int)(y0 + navHeight + 1)); } - void mousePressed() { + public void mousePressed() { super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) } - void mouseReleased() { + public void mouseReleased() { super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) } @@ -190,30 +157,28 @@ class W_AnalogRead extends Widget { analogModeButton.setOff(); } } -}; -//These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected -void VertScale_AR(int n) { - settings.arVertScaleSave = n; - for(int i = 0; i < w_analogRead.numAnalogReadBars; i++) { - w_analogRead.analogReadBars[i].adjustVertScale(w_analogRead.yLimOptions[n]); + public void setVerticalScale(int n) { + verticalScale = AnalogReadVerticalScale.values[n]; + for(int i = 0; i < analogReadBars.length; i++) { + analogReadBars[i].adjustVertScale(verticalScale.getValue()); + } } -} - -//triggered when there is an event in the LogLin Dropdown -void Duration_AR(int n) { - // println("adjust duration to: " + w_analogRead.analogReadBars[i].adjustTimeAxis(n)); - //set analog read x axis to the duration selected from dropdown - settings.arHorizScaleSave = n; - //Sync the duration of Time Series, Accelerometer, and Analog Read(Cyton Only) - for(int i = 0; i < w_analogRead.numAnalogReadBars; i++) { - if (n == 0) { - w_analogRead.analogReadBars[i].adjustTimeAxis(w_timeSeries.getTSHorizScale().getValue()); - } else { - w_analogRead.analogReadBars[i].adjustTimeAxis(w_analogRead.xLimOptions[n]); + public void setHorizontalScale(int n) { + horizontalScale = AnalogReadHorizontalScale.values[n]; + for(int i = 0; i < analogReadBars.length; i++) { + analogReadBars[i].adjustTimeAxis(horizontalScale.getValue()); } } +}; + +public void analogReadVerticalScaleDropdown(int n) { + w_analogRead.setVerticalScale(n); +} + +public void analogReadHorizontalScaleDropdown(int n) { + w_analogRead.setHorizontalScale(n); } //======================================================================================================================== diff --git a/OpenBCI_GUI/W_DigitalRead.pde b/OpenBCI_GUI/W_DigitalRead.pde index 69ea7f653..d3990aea9 100644 --- a/OpenBCI_GUI/W_DigitalRead.pde +++ b/OpenBCI_GUI/W_DigitalRead.pde @@ -10,15 +10,13 @@ class W_DigitalRead extends Widget { private int numDigitalReadDots; - float xF, yF, wF, hF; - int dot_padding; - //values for actual time series chart (rectangle encompassing all digitalReadDots) - float dot_x, dot_y, dot_h, dot_w; - float plotBottomWell; - float playbackWidgetHeight; - int digitalReaddotHeight; + private int dot_padding; + private float dot_x, dot_y, dot_h, dot_w; + private float plotBottomWell; + private float playbackWidgetHeight; + private int digitalReaddotHeight; - DigitalReadDot[] digitalReadDots; + private DigitalReadDot[] digitalReadDots; private Button digitalModeButton; @@ -31,16 +29,11 @@ class W_DigitalRead extends Widget { numDigitalReadDots = 5; - xF = float(x); //float(int( ... is a shortcut for rounding the float down... so that it doesn't creep into the 1px margin - yF = float(y); - wF = float(w); - hF = float(h); - dot_padding = 10; - dot_x = xF + dot_padding; - dot_y = yF + (dot_padding); - dot_w = wF - dot_padding*2; - dot_h = hF - playbackWidgetHeight - plotBottomWell - (dot_padding*2); + dot_x = float(x) + dot_padding; + dot_y = float(y) + (dot_padding); + dot_w = float(w) - dot_padding*2; + dot_h = float(h) - playbackWidgetHeight - plotBottomWell - (dot_padding*2); digitalReaddotHeight = int(dot_h/numDigitalReadDots); digitalReadDots = new DigitalReadDot[numDigitalReadDots]; @@ -113,28 +106,23 @@ class W_DigitalRead extends Widget { public void screenResized() { super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) - xF = float(x); //float(int( ... is a shortcut for rounding the float down... so that it doesn't creep into the 1px margin - yF = float(y); - wF = float(w); - hF = float(h); - - if (wF > hF) { - digitalReaddotHeight = int(hF/(numDigitalReadDots+1)); + if (w > h) { + digitalReaddotHeight = int(h/(numDigitalReadDots+1)); } else { - digitalReaddotHeight = int(wF/(numDigitalReadDots+1)); + digitalReaddotHeight = int(w/(numDigitalReadDots+1)); } if (numDigitalReadDots == 3) { - digitalReadDots[0].screenResized(x+int(wF*(1.0/3.0)), y+int(hF*(1.0/3.0)), digitalReaddotHeight, digitalReaddotHeight); //bar x, bar y, bar w, bar h - digitalReadDots[1].screenResized(x+int(wF/2), y+int(hF/2), digitalReaddotHeight, digitalReaddotHeight); //bar x, bar y, bar w, bar h - digitalReadDots[2].screenResized(x+int(wF*(2.0/3.0)), y+int(hF*(2.0/3.0)), digitalReaddotHeight, digitalReaddotHeight); //bar x, bar y, bar w, bar h + digitalReadDots[0].screenResized(x+int(w*(1.0/3.0)), y+int(h*(1.0/3.0)), digitalReaddotHeight, digitalReaddotHeight); //bar x, bar y, bar w, bar h + digitalReadDots[1].screenResized(x+int(w/2), y+int(h/2), digitalReaddotHeight, digitalReaddotHeight); //bar x, bar y, bar w, bar h + digitalReadDots[2].screenResized(x+int(w*(2.0/3.0)), y+int(h*(2.0/3.0)), digitalReaddotHeight, digitalReaddotHeight); //bar x, bar y, bar w, bar h } else { int y_pad = y + dot_padding; - digitalReadDots[0].screenResized(x+int(wF*(1.0/8.0)), y_pad+int(hF*(1.0/8.0)), digitalReaddotHeight, digitalReaddotHeight); - digitalReadDots[2].screenResized(x+int(wF/2), y_pad+int(hF/2), digitalReaddotHeight, digitalReaddotHeight); - digitalReadDots[4].screenResized(x+int(wF*(7.0/8.0)), y_pad+int(hF*(7.0/8.0)), digitalReaddotHeight, digitalReaddotHeight); - digitalReadDots[1].screenResized(digitalReadDots[0].dotX+int(wF*(3.0/16.0)), digitalReadDots[0].dotY+int(hF*(3.0/16.0)), digitalReaddotHeight, digitalReaddotHeight); - digitalReadDots[3].screenResized(digitalReadDots[2].dotX+int(wF*(3.0/16.0)), digitalReadDots[2].dotY+int(hF*(3.0/16.0)), digitalReaddotHeight, digitalReaddotHeight); + digitalReadDots[0].screenResized(x+int(w*(1.0/8.0)), y_pad+int(h*(1.0/8.0)), digitalReaddotHeight, digitalReaddotHeight); + digitalReadDots[2].screenResized(x+int(w/2), y_pad+int(h/2), digitalReaddotHeight, digitalReaddotHeight); + digitalReadDots[4].screenResized(x+int(w*(7.0/8.0)), y_pad+int(h*(7.0/8.0)), digitalReaddotHeight, digitalReaddotHeight); + digitalReadDots[1].screenResized(digitalReadDots[0].dotX+int(w*(3.0/16.0)), digitalReadDots[0].dotY+int(h*(3.0/16.0)), digitalReaddotHeight, digitalReaddotHeight); + digitalReadDots[3].screenResized(digitalReadDots[2].dotX+int(w*(3.0/16.0)), digitalReadDots[2].dotY+int(h*(3.0/16.0)), digitalReaddotHeight, digitalReaddotHeight); } diff --git a/OpenBCI_GUI/W_TimeSeries.pde b/OpenBCI_GUI/W_TimeSeries.pde index 61d63b3e8..7b32f8633 100644 --- a/OpenBCI_GUI/W_TimeSeries.pde +++ b/OpenBCI_GUI/W_TimeSeries.pde @@ -11,134 +11,6 @@ import org.apache.commons.lang3.math.NumberUtils; -public enum TimeSeriesXLim implements IndexingInterface -{ - ONE (0, 1, "1 sec"), - THREE (1, 3, "3 sec"), - FIVE (2, 5, "5 sec"), - TEN (3, 10, "10 sec"), - TWENTY (4, 20, "20 sec"); - - private int index; - private int value; - private String label; - private static TimeSeriesXLim[] vals = values(); - - TimeSeriesXLim(int _index, int _value, String _label) { - this.index = _index; - this.value = _value; - this.label = _label; - } - - public int getValue() { - return value; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } -} - -public enum TimeSeriesYLim implements IndexingInterface -{ - AUTO (0, 0, "Auto"), - UV_10(1, 10, "10 uV"), - UV_25(2, 25, "25 uV"), - UV_50 (3, 50, "50 uV"), - UV_100 (4, 100, "100 uV"), - UV_200 (5, 200, "200 uV"), - UV_400 (6, 400, "400 uV"), - UV_1000 (7, 1000, "1000 uV"), - UV_10000 (8, 10000, "10000 uV"); - - private int index; - private int value; - private String label; - private static TimeSeriesYLim[] vals = values(); - - TimeSeriesYLim(int _index, int _value, String _label) { - this.index = _index; - this.value = _value; - this.label = _label; - } - - public int getValue() { - return value; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } -} - -public enum TimeSeriesLabelMode implements IndexingInterface -{ - OFF (0, 0, "Off"), - MINIMAL (1, 1, "Minimal"), - ON (2, 2, "On"); - - private int index; - private int value; - private String label; - private static TimeSeriesLabelMode[] vals = values(); - - TimeSeriesLabelMode(int _index, int _value, String _label) { - this.index = _index; - this.value = _value; - this.label = _label; - } - - public int getValue() { - return value; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } -} - class W_timeSeries extends Widget { //to see all core variables/methods of the Widget class, refer to Widget.pde //put your custom variables here... @@ -206,9 +78,9 @@ class W_timeSeries extends Widget { numChannelBars = globalChannelCount; //set number of channel bars = to current globalChannelCount of system (4, 8, or 16) //This is a newer protocol for setting up dropdowns. - addDropdown("VertScale_TS", "Vert Scale", yLimit.getEnumStringsAsList(), yLimit.getIndex()); - addDropdown("Duration", "Window", xLimit.getEnumStringsAsList(), xLimit.getIndex()); - addDropdown("LabelMode_TS", "Labels", labelMode.getEnumStringsAsList(), labelMode.getIndex()); + addDropdown("timeSeriesVerticalScaleDropdown", "Vert Scale", yLimit.getEnumStringsAsList(), yLimit.getIndex()); + addDropdown("timeSeriesHorizontalScaleDropdown", "Window", xLimit.getEnumStringsAsList(), xLimit.getIndex()); + addDropdown("timeSeriesLabelModeDropdown", "Labels", labelMode.getEnumStringsAsList(), labelMode.getIndex()); //Instantiate scrollbar if using playback mode and scrollbar feature in use if((currentBoard instanceof FileBoard) && hasScrollbar) { @@ -256,7 +128,7 @@ class W_timeSeries extends Widget { adsSettingsController = new ADS1299SettingsController(_parent, tsChanSelect.getActiveChannels(), x_hsc, y_hsc, w_hsc, h_hsc, channelBarHeight); } - setTSVertScale(yLimit.getIndex()); + setVerticalScale(yLimit.getIndex()); } void update() { @@ -456,66 +328,48 @@ class W_timeSeries extends Widget { return myButton; } - public TimeSeriesYLim getTSVertScale() { + public TimeSeriesYLim getVerticalScale() { return yLimit; } - public TimeSeriesXLim getTSHorizScale() { + public TimeSeriesXLim getHorizontalScale() { return xLimit; } - public TimeSeriesLabelMode getTSLabelMode() { + public TimeSeriesLabelMode getLabelMode() { return labelMode; } - public void setTSVertScale(int n) { + public void setVerticalScale(int n) { yLimit = yLimit.values()[n]; for (int i = 0; i < numChannelBars; i++) { channelBars[i].adjustVertScale(yLimit.getValue()); } } - public void setTSHorizScale(int n) { + public void setHorizontalScale(int n) { xLimit = xLimit.values()[n]; for (int i = 0; i < numChannelBars; i++) { channelBars[i].adjustTimeAxis(xLimit.getValue()); } } - public void setTSLabelMode(int n) { + public void setLabelMode(int n) { labelMode = labelMode.values()[n]; } }; //These functions are activated when an item from the corresponding dropdown is selected -void VertScale_TS(int n) { - w_timeSeries.setTSVertScale(n); +void timeSeriesVerticalScaleDropdown(int n) { + w_timeSeries.setVerticalScale(n); } -//triggered when there is an event in the Duration Dropdown -void Duration(int n) { - w_timeSeries.setTSHorizScale(n); - - int newDuration = w_timeSeries.getTSHorizScale().getValue(); - //If selected by user, sync the duration of Time Series, Accelerometer, and Analog Read(Cyton Only) - if (currentBoard instanceof AccelerometerCapableBoard) { - if (settings.accHorizScaleSave == 0) { - //set accelerometer x axis to the duration selected from dropdown - w_accelerometer.accelerometerBar.adjustTimeAxis(newDuration); - } - } - if (currentBoard instanceof AnalogCapableBoard) { - if (settings.arHorizScaleSave == 0) { - //set analog read x axis to the duration selected from dropdown - for(int i = 0; i < w_analogRead.numAnalogReadBars; i++) { - w_analogRead.analogReadBars[i].adjustTimeAxis(newDuration); - } - } - } +void timeSeriesHorizontalScaleDropdown(int n) { + w_timeSeries.setHorizontalScale(n); } void LabelMode_TS(int n) { - w_timeSeries.setTSLabelMode(n); + w_timeSeries.setLabelMode(n); } //======================================================================================================================== From 2a9ba2772558978eea3fdc2b8404a74633400790 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 10 Apr 2025 15:33:22 -0500 Subject: [PATCH 05/29] Refactor SessionSettings global variable to be named sessionSettings to distinguish with upcoming widgetSettings --- OpenBCI_GUI/Containers.pde | 4 +-- OpenBCI_GUI/ControlPanel.pde | 18 +++++++------- OpenBCI_GUI/DataLogger.pde | 20 +++++++-------- OpenBCI_GUI/DataWriterBF.pde | 2 +- OpenBCI_GUI/DataWriterODF.pde | 8 +++--- OpenBCI_GUI/Interactivity.pde | 6 ++--- OpenBCI_GUI/OpenBCI_GUI.pde | 44 ++++++++++++++++----------------- OpenBCI_GUI/SessionSettings.pde | 26 +++++++++---------- OpenBCI_GUI/TopNav.pde | 10 ++++---- OpenBCI_GUI/W_Accelerometer.pde | 2 ++ OpenBCI_GUI/W_PacketLoss.pde | 2 +- OpenBCI_GUI/Widget.pde | 12 ++++----- OpenBCI_GUI/WidgetManager.pde | 6 ++--- 13 files changed, 80 insertions(+), 80 deletions(-) diff --git a/OpenBCI_GUI/Containers.pde b/OpenBCI_GUI/Containers.pde index 1dbd200e4..bd1bdbe71 100644 --- a/OpenBCI_GUI/Containers.pde +++ b/OpenBCI_GUI/Containers.pde @@ -80,8 +80,8 @@ void drawContainers() { if (widthOfLastScreen_C != width || heightOfLastScreen_C != height) { setupContainers(); //setupVizs(); //container extension example (more below) - settings.widthOfLastScreen = width; - settings.heightOfLastScreen = height; + sessionSettings.widthOfLastScreen = width; + sessionSettings.heightOfLastScreen = height; } } diff --git a/OpenBCI_GUI/ControlPanel.pde b/OpenBCI_GUI/ControlPanel.pde index cc5a30bd6..7623fcd9e 100644 --- a/OpenBCI_GUI/ControlPanel.pde +++ b/OpenBCI_GUI/ControlPanel.pde @@ -266,7 +266,7 @@ class ControlPanel { sb.append("OpenBCISession_"); sb.append(dataLogger.getSessionName()); sb.append(File.separator); - settings.setSessionPath(sb.toString()); + sessionSettings.setSessionPath(sb.toString()); } public void setBrainFlowStreamerOutput() { @@ -364,7 +364,7 @@ class DataSourceBox { Map bob = sourceList.getItem(int(sourceList.getValue())); String str = (String)bob.get("headline"); // Get the text displayed in the MenuList int newDataSource = (int)bob.get("value"); - settings.controlEventDataSource = str; //Used for output message on system start + sessionSettings.controlEventDataSource = str; //Used for output message on system start eegDataSource = newDataSource; //Reset protocol @@ -910,7 +910,7 @@ class SessionDataBox { createODFButton("odfButton", "OpenBCI", dataLogger.getDataLoggerOutputFormat(), x + padding, y + padding*2 + 18 + 58, (w-padding*3)/2, 24); createBDFButton("bdfButton", "BDF+", dataLogger.getDataLoggerOutputFormat(), x + padding*2 + (w-padding*3)/2, y + padding*2 + 18 + 58, (w-padding*3)/2, 24); - createMaxDurationDropdown("maxFileDuration", Arrays.asList(settings.fileDurations)); + createMaxDurationDropdown("maxFileDuration", Arrays.asList(sessionSettings.fileDurations)); @@ -1015,7 +1015,7 @@ class SessionDataBox { private void createMaxDurationDropdown(String name, List _items){ maxDurationDropdown = sessionData_cp5.addScrollableList(name) .setOpen(false) - .setColor(settings.dropdownColors) + .setColor(sessionSettings.dropdownColors) .setOutlineColor(150) //.setColorBackground(OPENBCI_BLUE) // text field bg color .setColorValueLabel(OPENBCI_DARKBLUE) // text color @@ -1033,7 +1033,7 @@ class SessionDataBox { maxDurationDropdown .getCaptionLabel() //the caption label is the text object in the primary bar .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!! - .setText(settings.fileDurations[settings.defaultOBCIMaxFileSize]) + .setText(sessionSettings.fileDurations[sessionSettings.defaultOBCIMaxFileSize]) .setFont(p4) .setSize(14) .getStyle() //need to grab style before affecting the paddingTop @@ -1042,7 +1042,7 @@ class SessionDataBox { maxDurationDropdown .getValueLabel() //the value label is connected to the text objects in the dropdown item bars .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!! - .setText(settings.fileDurations[settings.defaultOBCIMaxFileSize]) + .setText(sessionSettings.fileDurations[sessionSettings.defaultOBCIMaxFileSize]) .setFont(h5) .setSize(12) //set the font size of the item bars to 14pt .getStyle() //need to grab style before affecting the paddingTop @@ -1052,7 +1052,7 @@ class SessionDataBox { public void controlEvent(CallbackEvent theEvent) { if (theEvent.getAction() == ControlP5.ACTION_BROADCAST) { int n = (int)(theEvent.getController()).getValue(); - settings.setLogFileDurationChoice(n); + sessionSettings.setLogFileDurationChoice(n); println("ControlPanel: Chosen Recording Duration: " + n); } else if (theEvent.getAction() == ControlP5.ACTION_ENTER) { lockOutsideElements(true); @@ -1775,7 +1775,7 @@ class BrainFlowStreamerBox { private void createDropdown(String name){ bfFileSaveOption = bfStreamerCp5.addScrollableList(name) .setOpen(false) - .setColor(settings.dropdownColors) + .setColor(sessionSettings.dropdownColors) .setOutlineColor(150) .setSize(167, (dataWriterBfEnum.values().length + 1) * 24) .setBarHeight(24) //height of top/primary bar @@ -2164,7 +2164,7 @@ class SDBox { sdList = cp5_sdBox.addScrollableList(name) .setOpen(false) - .setColor(settings.dropdownColors) + .setColor(sessionSettings.dropdownColors) .setOutlineColor(150) .setSize(w - padding*2, 2*24)//temporary size .setBarHeight(24) //height of top/primary bar diff --git a/OpenBCI_GUI/DataLogger.pde b/OpenBCI_GUI/DataLogger.pde index 9656face3..7fc28e53a 100644 --- a/OpenBCI_GUI/DataLogger.pde +++ b/OpenBCI_GUI/DataLogger.pde @@ -33,7 +33,7 @@ class DataLogger { private void saveNewData() { //If data is available, save to playback file... - if(!settings.isLogFileOpen()) { + if(!sessionSettings.isLogFileOpen()) { return; } @@ -54,21 +54,21 @@ class DataLogger { } public void limitRecordingFileDuration() { - if (settings.isLogFileOpen() && outputDataSource == OUTPUT_SOURCE_ODF && settings.maxLogTimeReached()) { + if (sessionSettings.isLogFileOpen() && outputDataSource == OUTPUT_SOURCE_ODF && sessionSettings.maxLogTimeReached()) { println("DataLogging: Max recording duration reached for OpenBCI data format. Creating a new recording file in the session folder."); closeLogFile(); openNewLogFile(directoryManager.getFileNameDateTime()); - settings.setLogFileStartTime(System.nanoTime()); + sessionSettings.setLogFileStartTime(System.nanoTime()); } } public void onStartStreaming() { if (outputDataSource > OUTPUT_SOURCE_NONE && eegDataSource != DATASOURCE_PLAYBACKFILE) { //open data file if it has not already been opened - if (!settings.isLogFileOpen()) { + if (!sessionSettings.isLogFileOpen()) { openNewLogFile(directoryManager.getFileNameDateTime()); } - settings.setLogFileStartTime(System.nanoTime()); + sessionSettings.setLogFileStartTime(System.nanoTime()); } //Print BrainFlow Streamer Info here after ODF and BDF println @@ -111,7 +111,7 @@ class DataLogger { // Do nothing... break; } - settings.setLogFileIsOpen(true); + sessionSettings.setLogFileIsOpen(true); } /** @@ -159,7 +159,7 @@ class DataLogger { // Do nothing... break; } - settings.setLogFileIsOpen(false); + sessionSettings.setLogFileIsOpen(false); } private void closeLogFileBDF() { @@ -197,10 +197,10 @@ class DataLogger { } public void setBfWriterDefaultFolder() { - if (settings.getSessionPath() != "") { - settings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + sessionName); + if (sessionSettings.getSessionPath() != "") { + sessionSettings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + sessionName); } - fileWriterBF.setBrainFlowStreamerFolderName(sessionName, settings.getSessionPath()); + fileWriterBF.setBrainFlowStreamerFolderName(sessionName, sessionSettings.getSessionPath()); } public String getBfWriterFilePath() { diff --git a/OpenBCI_GUI/DataWriterBF.pde b/OpenBCI_GUI/DataWriterBF.pde index 83f28f11c..f4c136a18 100644 --- a/OpenBCI_GUI/DataWriterBF.pde +++ b/OpenBCI_GUI/DataWriterBF.pde @@ -48,7 +48,7 @@ public class DataWriterBF { } public void setBrainFlowStreamerFolderName(String _folderName, String _folderPath) { - //settings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); + //sessionSettings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); folderName = _folderName; folderPath = _folderPath; diff --git a/OpenBCI_GUI/DataWriterODF.pde b/OpenBCI_GUI/DataWriterODF.pde index 7d7e03bcf..2555ea9e4 100644 --- a/OpenBCI_GUI/DataWriterODF.pde +++ b/OpenBCI_GUI/DataWriterODF.pde @@ -7,8 +7,8 @@ public class DataWriterODF { protected String headerFirstLineString = "%OpenBCI Raw EXG Data"; DataWriterODF(String _sessionName, String _fileName) { - settings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); - fname = settings.getSessionPath(); + sessionSettings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); + fname = sessionSettings.getSessionPath(); fname += fileNamePrependString; fname += _fileName; fname += ".txt"; @@ -21,8 +21,8 @@ public class DataWriterODF { DataWriterODF(String _sessionName, String _fileName, String _fileNamePrependString, String _headerFirstLineString) { fileNamePrependString = _fileNamePrependString; headerFirstLineString = _headerFirstLineString; - settings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); - fname = settings.getSessionPath(); + sessionSettings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); + fname = sessionSettings.getSessionPath(); fname += fileNamePrependString; fname += _fileName; fname += ".txt"; diff --git a/OpenBCI_GUI/Interactivity.pde b/OpenBCI_GUI/Interactivity.pde index 9145bf914..4abafaef5 100644 --- a/OpenBCI_GUI/Interactivity.pde +++ b/OpenBCI_GUI/Interactivity.pde @@ -101,14 +101,14 @@ void parseKey(char val) { ///////////////////// Save User settings lowercase n case 'n': println("Interactivity: Save key pressed!"); - settings.save(settings.getPath("User", eegDataSource, globalChannelCount)); - outputSuccess("Settings Saved! Using Expert Mode, you can load these settings using 'N' key. Click \"Default\" to revert to factory settings."); + sessionSettings.save(sessionSettings.getPath("User", eegDataSource, globalChannelCount)); + outputSuccess("Settings Saved! Using Expert Mode, you can load these settings using 'N' key. Click \"Default\" to revert to factory sessionSettings."); return; ///////////////////// Load User settings uppercase N case 'N': println("Interactivity: Load key pressed!"); - settings.loadKeyPressed(); + sessionSettings.loadKeyPressed(); return; case '?': diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde index d419af2b9..b5b14ae82 100644 --- a/OpenBCI_GUI/OpenBCI_GUI.pde +++ b/OpenBCI_GUI/OpenBCI_GUI.pde @@ -293,7 +293,7 @@ public final static String stopButton_pressToStop_txt = "Stop Data Stream"; public final static String stopButton_pressToStart_txt = "Start Data Stream"; DirectoryManager directoryManager; -SessionSettings settings; +SessionSettings sessionSettings; GuiSettings guiSettings; DataProcessing dataProcessing; FilterSettings filterSettings; @@ -428,7 +428,7 @@ void setup() { // Copy sample data to the Users' Documents folder + create Recordings folder directoryManager.init(); - settings = new SessionSettings(); + sessionSettings = new SessionSettings(); guiSettings = new GuiSettings(directoryManager.getSettingsPath()); userPlaybackHistoryFile = directoryManager.getSettingsPath()+"UserPlaybackHistory.json"; @@ -445,8 +445,8 @@ void delayedSetup() { smooth(); //turn this off if it's too slow surface.setResizable(true); //updated from frame.setResizable in Processing 2 - settings.widthOfLastScreen = width; //for screen resizing (Thank's Tao) - settings.heightOfLastScreen = height; + sessionSettings.widthOfLastScreen = width; //for screen resizing (Thank's Tao) + sessionSettings.heightOfLastScreen = height; setupContainers(); @@ -533,8 +533,8 @@ synchronized void draw() { dataProcessing.networkingDataAccumulator.compareAndSetNetworkingFrameLocks(); } } else if (systemMode == SYSTEMMODE_INTROANIMATION) { - if (settings.introAnimationInit == 0) { - settings.introAnimationInit = millis(); + if (sessionSettings.introAnimationInit == 0) { + sessionSettings.introAnimationInit = millis(); } else { introAnimation(); } @@ -750,12 +750,12 @@ void initSystem() { //don't save default session settings for StreamingBoard if (eegDataSource != DATASOURCE_STREAMING) { //Init software settings: create default settings file that is datasource unique - settings.init(); + sessionSettings.init(); verbosePrint("OpenBCI_GUI: initSystem: Session settings initialized"); } if (guiSettings.getAutoLoadSessionSettings()) { - settings.autoLoadSessionSettings(); + sessionSettings.autoLoadSessionSettings(); verbosePrint("OpenBCI_GUI: initSystem: User default session settings automatically loaded"); } @@ -970,10 +970,10 @@ void systemUpdate() { // for updating data values and variables //updates while in system control panel before START SYSTEM controlPanel.update(); - if (settings.widthOfLastScreen != width || settings.heightOfLastScreen != height) { + if (sessionSettings.widthOfLastScreen != width || sessionSettings.heightOfLastScreen != height) { topNav.screenHasBeenResized(width, height); - settings.widthOfLastScreen = width; - settings.heightOfLastScreen = height; + sessionSettings.widthOfLastScreen = width; + sessionSettings.heightOfLastScreen = height; //println("W = " + width + " || H = " + height); } } @@ -982,19 +982,19 @@ void systemUpdate() { // for updating data values and variables //alternative component listener function (line 177 mouseReleased- 187 frame.addComponentListener) for processing 3, //Component listener doesn't seem to work, so staying with this method for now - if (settings.widthOfLastScreen != width || settings.heightOfLastScreen != height) { - settings.screenHasBeenResized = true; - settings.timeOfLastScreenResize = millis(); - settings.widthOfLastScreen = width; - settings.heightOfLastScreen = height; + if (sessionSettings.widthOfLastScreen != width || sessionSettings.heightOfLastScreen != height) { + sessionSettings.screenHasBeenResized = true; + sessionSettings.timeOfLastScreenResize = millis(); + sessionSettings.widthOfLastScreen = width; + sessionSettings.heightOfLastScreen = height; } //re-initialize GUI if screen has been resized and it's been more than 1/2 seccond (to prevent reinitialization of GUI from happening too often) - if (settings.screenHasBeenResized && settings.timeOfLastScreenResize + 500 > millis()) { + if (sessionSettings.screenHasBeenResized && sessionSettings.timeOfLastScreenResize + 500 > millis()) { ourApplet = this; //reset PApplet... topNav.screenHasBeenResized(width, height); wm.screenResized(); - settings.screenHasBeenResized = false; + sessionSettings.screenHasBeenResized = false; } if (wm.isWMInitialized) { @@ -1064,7 +1064,7 @@ void systemInitSession() { //Global function to update the number of channels void updateGlobalChannelCount(int _channelCount) { globalChannelCount = _channelCount; - settings.sessionSettingsChannelCount = _channelCount; //used in SoftwareSettings.pde only + sessionSettings.sessionSettingsChannelCount = _channelCount; //used in SoftwareSettings.pde only fftBuff = new ddf.minim.analysis.FFT[globalChannelCount]; //reinitialize the FFT buffer println("OpenBCI_GUI: Channel count set to " + str(globalChannelCount)); } @@ -1076,8 +1076,8 @@ void introAnimation() { int t1 = 0; float transparency = 0; - if (millis() >= settings.introAnimationInit) { - transparency = map(millis() - settings.introAnimationInit, t1, settings.introAnimationDuration, 0, 255); + if (millis() >= sessionSettings.introAnimationInit) { + transparency = map(millis() - sessionSettings.introAnimationInit, t1, sessionSettings.introAnimationDuration, 0, 255); verbosePrint(String.valueOf(transparency)); tint(255, transparency); //draw OpenBCI Logo Front & Center @@ -1091,7 +1091,7 @@ void introAnimation() { } //Exit intro animation when the duration has expired AND the Control Panel is ready - if ((millis() >= settings.introAnimationInit + settings.introAnimationDuration) + if ((millis() >= sessionSettings.introAnimationInit + sessionSettings.introAnimationDuration) && controlPanel != null) { systemMode = SYSTEMMODE_PREINIT; controlPanel.open(); diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde index acfc8a7f0..b85dd4e70 100644 --- a/OpenBCI_GUI/SessionSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -55,7 +55,7 @@ class SessionSettings { private long logFileStartTime; private long logFileMaxDurationNano = -1; //this is a global CColor that determines the style of all widget dropdowns ... this should go in WidgetManager.pde - CColor dropdownColors = new CColor(); + public CColor dropdownColors = new CColor(); //default configuration settings file location and file name variables private String sessionPath = ""; @@ -969,7 +969,7 @@ class SessionSettings { void saveButtonPressed() { if (saveDialogName == null) { - File fileToSave = dataFile(settings.getPath("User", eegDataSource, globalChannelCount)); + File fileToSave = dataFile(sessionSettings.getPath("User", eegDataSource, globalChannelCount)); FileChooser chooser = new FileChooser( FileChooserMode.SAVE, "saveConfigFile", @@ -1033,10 +1033,10 @@ void saveConfigFile(File selection) { println("SessionSettings: saveConfigFile: Window was closed or the user hit cancel."); } else { println("SessionSettings: saveConfigFile: User selected " + selection.getAbsolutePath()); - settings.saveDialogName = selection.getAbsolutePath(); - settings.save(settings.saveDialogName); //save current settings to JSON file in SavedData + sessionSettings.saveDialogName = selection.getAbsolutePath(); + sessionSettings.save(sessionSettings.saveDialogName); //save current settings to JSON file in SavedData outputSuccess("Settings Saved! Using Expert Mode, you can load these settings using 'N' key. Click \"Default\" to revert to factory settings."); //print success message to screen - settings.saveDialogName = null; //reset this variable for future use + sessionSettings.saveDialogName = null; //reset this variable for future use } } // Select file to load custom settings using dropdown in TopNav.pde @@ -1046,26 +1046,26 @@ void loadConfigFile(File selection) { } else { println("SessionSettings: loadConfigFile: User selected " + selection.getAbsolutePath()); //output("You have selected \"" + selection.getAbsolutePath() + "\" to Load custom settings."); - settings.loadDialogName = selection.getAbsolutePath(); + sessionSettings.loadDialogName = selection.getAbsolutePath(); try { - settings.load(settings.loadDialogName); //load settings from JSON file in /data/ + sessionSettings.load(sessionSettings.loadDialogName); //load settings from JSON file in /data/ //Output success message when Loading settings is complete without errors - if (settings.chanNumError == false - && settings.dataSourceError == false - && settings.loadErrorCytonEvent == false) { + if (sessionSettings.chanNumError == false + && sessionSettings.dataSourceError == false + && sessionSettings.loadErrorCytonEvent == false) { outputSuccess("Settings Loaded!"); } } catch (Exception e) { println("SessionSettings: Incompatible settings file or other error"); - if (settings.chanNumError == true) { + if (sessionSettings.chanNumError == true) { outputError("Settings Error: Channel Number Mismatch Detected"); - } else if (settings.dataSourceError == true) { + } else if (sessionSettings.dataSourceError == true) { outputError("Settings Error: Data Source Mismatch Detected"); } else { outputError("Error trying to load settings file, possibly from previous GUI. Removing old settings."); if (selection.exists()) selection.delete(); } } - settings.loadDialogName = null; //reset this variable for future use + sessionSettings.loadDialogName = null; //reset this variable for future use } } \ No newline at end of file diff --git a/OpenBCI_GUI/TopNav.pde b/OpenBCI_GUI/TopNav.pde index dabd4e12a..5a0d07df4 100644 --- a/OpenBCI_GUI/TopNav.pde +++ b/OpenBCI_GUI/TopNav.pde @@ -729,7 +729,7 @@ class LayoutSelector { output("Layout [" + (layoutNumber) + "] selected."); toggleVisibility(); //shut layoutSelector if something is selected wm.setNewContainerLayout(layoutNumber); //have WidgetManager update Layout and active widgets - settings.currentLayout = layoutNumber; //copy this value to be used when saving Layout setting + sessionSettings.currentLayout = layoutNumber; //copy this value to be used when saving Layout setting } }); layoutOptions.add(tempLayoutButton); @@ -980,7 +980,7 @@ class ConfigSelector { saveSessionSettings.onRelease(new CallbackListener() { public void controlEvent(CallbackEvent theEvent) { toggleVisibility(); - settings.saveButtonPressed(); + sessionSettings.saveButtonPressed(); } }); saveSessionSettings.setDescription("Expert Mode enables advanced keyboard shortcuts and access to all GUI features."); @@ -991,7 +991,7 @@ class ConfigSelector { loadSessionSettings.onRelease(new CallbackListener() { public void controlEvent(CallbackEvent theEvent) { toggleVisibility(); - settings.loadButtonPressed(); + sessionSettings.loadButtonPressed(); } }); loadSessionSettings.setDescription("Expert Mode enables advanced keyboard shortcuts and access to all GUI features."); @@ -1002,7 +1002,7 @@ class ConfigSelector { defaultSessionSettings.onRelease(new CallbackListener() { public void controlEvent(CallbackEvent theEvent) { toggleVisibility(); - settings.defaultButtonPressed(); + sessionSettings.defaultButtonPressed(); } }); defaultSessionSettings.setDescription("Expert Mode enables advanced keyboard shortcuts and access to all GUI features."); @@ -1043,7 +1043,7 @@ class ConfigSelector { //Shorten height of this box h -= margin*4 + b_h*3; //User has selected Are You Sure?->Yes - settings.clearAll(); + sessionSettings.clearAll(); guiSettings.resetAllSettings(); clearAllSettingsPressed = false; //Stop the system if the user clears all settings diff --git a/OpenBCI_GUI/W_Accelerometer.pde b/OpenBCI_GUI/W_Accelerometer.pde index 57b6bbd81..e3e5a5297 100644 --- a/OpenBCI_GUI/W_Accelerometer.pde +++ b/OpenBCI_GUI/W_Accelerometer.pde @@ -49,6 +49,8 @@ class W_Accelerometer extends Widget { accelBoard = (AccelerometerCapableBoard)currentBoard; + + //Make dropdowns addDropdown("accelerometerVerticalScaleDropdown", "Vert Scale", verticalScale.getEnumStringsAsList(), verticalScale.getIndex()); addDropdown("accelerometerHorizontalScaleDropdown", "Window", horizontalScale.getEnumStringsAsList(), horizontalScale.getIndex()); diff --git a/OpenBCI_GUI/W_PacketLoss.pde b/OpenBCI_GUI/W_PacketLoss.pde index ae8c0d943..9b4882685 100644 --- a/OpenBCI_GUI/W_PacketLoss.pde +++ b/OpenBCI_GUI/W_PacketLoss.pde @@ -63,7 +63,7 @@ class W_PacketLoss extends Widget { tableDropdown = cp5_widget.addScrollableList("TableTimeWindow") .setDrawOutline(false) .setOpen(false) - .setColor(settings.dropdownColors) + .setColor(sessionSettings.dropdownColors) .setOutlineColor(OBJECT_BORDER_GREY) .setBarHeight(CELL_HEIGHT) //height of top/primary bar .setItemHeight(CELL_HEIGHT) //height of all item/dropdown bars diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index 82bb4ad66..ab1867514 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -13,7 +13,7 @@ interface IndexingInterface { public String getString(); } -class Widget{ +class Widget { protected PApplet pApplet; @@ -46,11 +46,9 @@ class Widget{ cp5_widget = new ControlP5(pApplet); cp5_widget.setAutoDraw(false); //this prevents the cp5 object from drawing automatically (if it is set to true it will be drawn last, on top of all other GUI stuff... not good) dropdowns = new ArrayList(); - //setup dropdown menus currentContainer = 5; //central container by default mapToCurrentContainer(); - } public boolean getIsActive() { @@ -88,12 +86,12 @@ class Widget{ } public void setupWidgetSelectorDropdown(ArrayList _widgetOptions){ - cp5_widget.setColor(settings.dropdownColors); + cp5_widget.setColor(sessionSettings.dropdownColors); ScrollableList scrollList = cp5_widget.addScrollableList("WidgetSelector") .setPosition(x0+2, y0+2) //upper left corner // .setFont(h2) .setOpen(false) - .setColor(settings.dropdownColors) + .setColor(sessionSettings.dropdownColors) .setOutlineColor(OBJECT_BORDER_GREY) //.setSize(widgetSelectorWidth, int(h0 * widgetDropdownScaling) )// + maxFreqList.size()) //.setSize(widgetSelectorWidth, (NUM_WIDGETS_TO_SHOW+1)*(navH-4) )// + maxFreqList.size()) @@ -123,7 +121,7 @@ class Widget{ } public void setupNavDropdowns(){ - cp5_widget.setColor(settings.dropdownColors); + cp5_widget.setColor(sessionSettings.dropdownColors); // println("Setting up dropdowns..."); for(int i = 0; i < dropdowns.size(); i++){ int dropdownPos = dropdowns.size() - i; @@ -132,7 +130,7 @@ class Widget{ .setPosition(x0+w0-(dropdownWidth*(dropdownPos))-(2*(dropdownPos)), y0 + navH + 2) //float right .setFont(h5) .setOpen(false) - .setColor(settings.dropdownColors) + .setColor(sessionSettings.dropdownColors) .setOutlineColor(OBJECT_BORDER_GREY) .setSize(dropdownWidth, (dropdowns.get(i).items.size()+1)*(navH-4) )// + maxFreqList.size()) .setBarHeight(navH-4) diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index 5678e5c44..43ebf95de 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -57,15 +57,15 @@ class WidgetManager{ if(globalChannelCount == 4 && eegDataSource == DATASOURCE_GANGLION) { currentContainerLayout = 1; - settings.currentLayout = 1; // used for save/load settings + sessionSettings.currentLayout = 1; // used for save/load settings setNewContainerLayout(currentContainerLayout); //sets and fills layout with widgets in order of widget index, to reorganize widget index, reorder the creation in setupWidgets() } else if (eegDataSource == DATASOURCE_PLAYBACKFILE) { currentContainerLayout = 1; - settings.currentLayout = 1; // used for save/load settings + sessionSettings.currentLayout = 1; // used for save/load settings setNewContainerLayout(currentContainerLayout); //sets and fills layout with widgets in order of widget index, to reorganize widget index, reorder the creation in setupWidgets() } else { currentContainerLayout = 4; //default layout ... tall container left and 2 shorter containers stacked on the right - settings.currentLayout = 4; // used for save/load settings + sessionSettings.currentLayout = 4; // used for save/load settings setNewContainerLayout(currentContainerLayout); //sets and fills layout with widgets in order of widget index, to reorganize widget index, reorder the creation in setupWidgets() } From 6b83bc38ff686022d0046009752f7214fcf9e385 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 10 Apr 2025 16:22:47 -0500 Subject: [PATCH 06/29] Remove usage of pApplet from Widget class and instead use global existing ourApplet variable and clean up legacy comments in Widgets --- OpenBCI_GUI/ADS1299SettingsController.pde | 4 +- OpenBCI_GUI/ChannelSelect.pde | 18 +++--- OpenBCI_GUI/FifoChannelBar.pde | 12 ++-- OpenBCI_GUI/OpenBCI_GUI.pde | 8 +-- OpenBCI_GUI/W_Accelerometer.pde | 22 ++++---- OpenBCI_GUI/W_AnalogRead.pde | 20 +++---- OpenBCI_GUI/W_BandPower.pde | 18 +++--- OpenBCI_GUI/W_CytonImpedance.pde | 18 +++--- OpenBCI_GUI/W_DigitalRead.pde | 18 +++--- OpenBCI_GUI/W_EMG.pde | 19 +++---- OpenBCI_GUI/W_EMGJoystick.pde | 10 ++-- OpenBCI_GUI/W_FFT.pde | 24 ++++---- OpenBCI_GUI/W_Focus.pde | 18 +++--- OpenBCI_GUI/W_GanglionImpedance.pde | 18 ++---- OpenBCI_GUI/W_HeadPlot.pde | 14 ++--- OpenBCI_GUI/W_Marker.pde | 10 ++-- OpenBCI_GUI/W_PacketLoss.pde | 14 ++--- OpenBCI_GUI/W_Playback.pde | 14 ++--- OpenBCI_GUI/W_PulseSensor.pde | 10 ++-- OpenBCI_GUI/W_Spectrogram.pde | 22 ++++---- OpenBCI_GUI/W_Template.pde | 14 ++--- OpenBCI_GUI/W_TimeSeries.pde | 34 ++++++------ OpenBCI_GUI/Widget.pde | 14 ++--- OpenBCI_GUI/WidgetManager.pde | 68 +++++++++-------------- 24 files changed, 202 insertions(+), 239 deletions(-) diff --git a/OpenBCI_GUI/ADS1299SettingsController.pde b/OpenBCI_GUI/ADS1299SettingsController.pde index b2f9b69fc..972a4065c 100644 --- a/OpenBCI_GUI/ADS1299SettingsController.pde +++ b/OpenBCI_GUI/ADS1299SettingsController.pde @@ -64,14 +64,14 @@ class ADS1299SettingsController { protected int channelCount; protected List activeChannels; - ADS1299SettingsController(PApplet _parent, List _activeChannels, int _x, int _y, int _w, int _h, int _channelBarHeight) { + ADS1299SettingsController(PApplet _parentApplet, List _activeChannels, int _x, int _y, int _w, int _h, int _channelBarHeight) { x = _x; y = _y; w = _w; h = _h; channelBarHeight = _channelBarHeight; - _parentApplet = _parent; + _parentApplet = _parentApplet; hwsCp5 = new ControlP5(_parentApplet); hwsCp5.setGraphics(_parentApplet, 0,0); hwsCp5.setAutoDraw(false); diff --git a/OpenBCI_GUI/ChannelSelect.pde b/OpenBCI_GUI/ChannelSelect.pde index d82aa4606..4b35e4764 100644 --- a/OpenBCI_GUI/ChannelSelect.pde +++ b/OpenBCI_GUI/ChannelSelect.pde @@ -10,7 +10,7 @@ class ChannelSelect { protected boolean channelSelectHover; protected boolean isVisible; - ChannelSelect(PApplet _parent, int _x, int _y, int _w, int _navH) { + ChannelSelect(PApplet _parentApplet, int _x, int _y, int _w, int _navH) { x = _x; y = _y; w = _w; @@ -18,8 +18,8 @@ class ChannelSelect { navH = _navH; //setup for checkboxes - cp5_chanSelect = new ControlP5(_parent); - cp5_chanSelect.setGraphics(_parent, 0, 0); + cp5_chanSelect = new ControlP5(_parentApplet); + cp5_chanSelect.setGraphics(_parentApplet, 0, 0); cp5_chanSelect.setAutoDraw(false); //draw only when specified } @@ -66,8 +66,8 @@ class ChannelSelect { popStyle(); } - public void screenResized(PApplet _parent) { - cp5_chanSelect.setGraphics(_parent, 0, 0); + public void screenResized(PApplet _parentApplet) { + cp5_chanSelect.setGraphics(_parentApplet, 0, 0); } public void mousePressed(boolean dropdownIsActive) { @@ -111,8 +111,8 @@ class ExGChannelSelect extends ChannelSelect { protected List channelButtons; private List activeChannels = new ArrayList(); - ExGChannelSelect(PApplet _parent, int _x, int _y, int _w, int _navH) { - super(_parent, _x, _y, _w, _navH); + ExGChannelSelect(PApplet _parentApplet, int _x, int _y, int _w, int _navH) { + super(_parentApplet, _x, _y, _w, _navH); createButtons(); } @@ -282,8 +282,8 @@ class DualExGChannelSelect extends ExGChannelSelect { DualChannelSelector dualChannelSelector; - DualExGChannelSelect(PApplet _parent, int _x, int _y, int _w, int _navH, boolean isFirstRow) { - super(_parent, _x, _y, _w, _navH); + DualExGChannelSelect(PApplet _parentApplet, int _x, int _y, int _w, int _navH, boolean isFirstRow) { + super(_parentApplet, _x, _y, _w, _navH); dualChannelSelector = new DualChannelSelector(isFirstRow); } diff --git a/OpenBCI_GUI/FifoChannelBar.pde b/OpenBCI_GUI/FifoChannelBar.pde index 846fc8fae..f9debcc13 100644 --- a/OpenBCI_GUI/FifoChannelBar.pde +++ b/OpenBCI_GUI/FifoChannelBar.pde @@ -25,7 +25,7 @@ class FifoChannelBar { private GPlotAutoscaler gplotAutoscaler = new GPlotAutoscaler(); - FifoChannelBar(PApplet _parent, String yAxisLabel, int xLimit, float yLimit, int _x, int _y, int _w, int _h, color lineColor, int _layerCount, int _samplingRate, int _totalBufferSeconds) { + FifoChannelBar(PApplet _parentApplet, String yAxisLabel, int xLimit, float yLimit, int _x, int _y, int _w, int _h, color lineColor, int _layerCount, int _samplingRate, int _totalBufferSeconds) { x = _x; y = _y; w = _w; @@ -42,7 +42,7 @@ class FifoChannelBar { isOpenness = true; } - plot = new GPlot(_parent); + plot = new GPlot(_parentApplet); plot.setPos(x + 36 + 4 + xOffset, y); plot.setDim(w - 36 - 4 - xOffset, h); plot.setMar(0f, 0f, 0f, 0f); @@ -77,12 +77,12 @@ class FifoChannelBar { valueTextBox.setVisible(false); } - FifoChannelBar(PApplet _parent, String yAxisLabel, int xLimit, int _x, int _y, int _w, int _h, color lineColor, int layerCount, int _totalBufferSeconds) { - this(_parent, yAxisLabel, xLimit, 1, _x, _y, _w, _h, lineColor, layerCount, 200, _totalBufferSeconds); + FifoChannelBar(PApplet _parentApplet, String yAxisLabel, int xLimit, int _x, int _y, int _w, int _h, color lineColor, int layerCount, int _totalBufferSeconds) { + this(_parentApplet, yAxisLabel, xLimit, 1, _x, _y, _w, _h, lineColor, layerCount, 200, _totalBufferSeconds); } - FifoChannelBar(PApplet _parent, String yAxisLabel, int xLimit, float yLimit, int _x, int _y, int _w, int _h, color lineColor, int _totalBufferSeconds) { - this(_parent, yAxisLabel, xLimit, yLimit, _x, _y, _w, _h, lineColor, 1, 200, _totalBufferSeconds); + FifoChannelBar(PApplet _parentApplet, String yAxisLabel, int xLimit, float yLimit, int _x, int _y, int _w, int _h, color lineColor, int _totalBufferSeconds) { + this(_parentApplet, yAxisLabel, xLimit, yLimit, _x, _y, _w, _h, lineColor, 1, 200, _totalBufferSeconds); } private void initArrays() { diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde index b5b14ae82..b56effd1f 100644 --- a/OpenBCI_GUI/OpenBCI_GUI.pde +++ b/OpenBCI_GUI/OpenBCI_GUI.pde @@ -342,6 +342,9 @@ void settings() { } void setup() { + + ourApplet = this; + frameRate(120); copyPaste = new CopyPaste(); @@ -432,9 +435,6 @@ void setup() { guiSettings = new GuiSettings(directoryManager.getSettingsPath()); userPlaybackHistoryFile = directoryManager.getSettingsPath()+"UserPlaybackHistory.json"; - //open window - ourApplet = this; - // Bug #426: If setup takes too long, JOGL will time out waiting for the GUI to draw something. // moving the setup to a separate thread solves this. We just have to make sure not to // start drawing until delayed setup is done. @@ -743,7 +743,7 @@ void initSystem() { topNav.controlPanelCollapser.setOff(); verbosePrint("OpenBCI_GUI: initSystem: -- Init 4 -- " + millis()); - wm = new WidgetManager(this); + wm = new WidgetManager(); verbosePrint("OpenBCI_GUI: initSystem: -- Init 5 -- " + millis()); diff --git a/OpenBCI_GUI/W_Accelerometer.pde b/OpenBCI_GUI/W_Accelerometer.pde index e3e5a5297..8d7f7227f 100644 --- a/OpenBCI_GUI/W_Accelerometer.pde +++ b/OpenBCI_GUI/W_Accelerometer.pde @@ -44,13 +44,11 @@ class W_Accelerometer extends Widget { private AccelerometerCapableBoard accelBoard; - W_Accelerometer(PApplet _parent) { - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_Accelerometer(String _widgetName) { + super(_widgetName); accelBoard = (AccelerometerCapableBoard)currentBoard; - - //Make dropdowns addDropdown("accelerometerVerticalScaleDropdown", "Vert Scale", verticalScale.getEnumStringsAsList(), verticalScale.getIndex()); addDropdown("accelerometerHorizontalScaleDropdown", "Window", horizontalScale.getEnumStringsAsList(), horizontalScale.getIndex()); @@ -62,7 +60,7 @@ class W_Accelerometer extends Widget { lastDataSampleValues = new float[NUM_ACCEL_DIMS]; //Create our channel bar and populate our accelerometerBar array - accelerometerBar = new AccelerometerBar(_parent, verticalScale.getHighestValue(), accelGraphX, accelGraphY, accelGraphWidth, accelGraphHeight); + accelerometerBar = new AccelerometerBar(ourApplet, verticalScale.getHighestValue(), accelGraphX, accelGraphY, accelGraphWidth, accelGraphHeight); accelerometerBar.adjustTimeAxis(horizontalScale.getValue()); accelerometerBar.adjustVertScale(verticalScale.getValue()); @@ -82,7 +80,7 @@ class W_Accelerometer extends Widget { } void update() { - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); if (accelBoard.isAccelerometerActive()) { //update the line graph and corresponding gplot points @@ -109,7 +107,7 @@ class W_Accelerometer extends Widget { } void draw() { - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); pushStyle(); @@ -159,7 +157,7 @@ class W_Accelerometer extends Widget { int prevY = y; int prevW = w; int prevH = h; - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); setGraphDimensions(); //resize the accelerometer line graph accelerometerBar.screenResized(accelGraphX, accelGraphY, accelGraphWidth, accelGraphHeight); //bar x, bar y, bar w, bar h @@ -168,11 +166,11 @@ class W_Accelerometer extends Widget { } void mousePressed() { - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); } void mouseReleased() { - super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) + super.mouseReleased(); } private void createAccelModeButton(String name, String text, int _x, int _y, int _w, int _h, PFont _font, int _fontSize, color _bg, color _textColor) { @@ -316,7 +314,7 @@ class AccelerometerBar { private AccelerometerCapableBoard accelBoard; - AccelerometerBar(PApplet _parent, float _yLimit, int _x, int _y, int _w, int _h) { //channel number, x/y location, height, width + AccelerometerBar(PApplet _parentApplet, float _yLimit, int _x, int _y, int _w, int _h) { //channel number, x/y location, height, width // This widget is only instantiated when the board is accel capable, so we don't need to check accelBoard = (AccelerometerCapableBoard)currentBoard; @@ -326,7 +324,7 @@ class AccelerometerBar { w = _w; h = _h; - plot = new GPlot(_parent); + plot = new GPlot(_parentApplet); plot.setPos(x + 36 + 4, y); //match Accelerometer plot position with Time Series plot.setDim(w - 36 - 4, h); plot.setMar(0f, 0f, 0f, 0f); diff --git a/OpenBCI_GUI/W_AnalogRead.pde b/OpenBCI_GUI/W_AnalogRead.pde index c06d991df..1f012c58e 100644 --- a/OpenBCI_GUI/W_AnalogRead.pde +++ b/OpenBCI_GUI/W_AnalogRead.pde @@ -28,8 +28,8 @@ class W_AnalogRead extends Widget { private AnalogCapableBoard analogBoard; - W_AnalogRead(PApplet _parent) { - super(_parent); + W_AnalogRead(String _widgetName) { + super(_widgetName); analogBoard = (AnalogCapableBoard)currentBoard; @@ -49,7 +49,7 @@ class W_AnalogRead extends Widget { //create our channel bars and populate our analogReadBars array! for(int i = 0; i < analogReadBars.length; i++) { int analogReadBarY = int(ar_y) + i*(analogReadBarHeight); //iterate through bar locations - AnalogReadBar tempBar = new AnalogReadBar(_parent, i+5, int(ar_x), analogReadBarY, int(ar_w), analogReadBarHeight); //int _channelNumber, int _x, int _y, int _w, int _h + AnalogReadBar tempBar = new AnalogReadBar(ourApplet, i+5, int(ar_x), analogReadBarY, int(ar_w), analogReadBarHeight); //int _channelNumber, int _x, int _y, int _w, int _h analogReadBars[i] = tempBar; } @@ -60,7 +60,7 @@ class W_AnalogRead extends Widget { } public void update() { - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); if (currentBoard instanceof DataSourcePlayback) { if (((DataSourcePlayback)currentBoard) instanceof AnalogCapableBoard @@ -87,7 +87,7 @@ class W_AnalogRead extends Widget { } public void draw() { - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); //remember to refer to x,y,w,h which are the positioning variables of the Widget class if (analogBoard.isAnalogActive()) { @@ -98,7 +98,7 @@ class W_AnalogRead extends Widget { } public void screenResized() { - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); ar_x = float(x) + arPadding; ar_y = float(y) + (arPadding); @@ -115,11 +115,11 @@ class W_AnalogRead extends Widget { } public void mousePressed() { - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); } public void mouseReleased() { - super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) + super.mouseReleased(); } private void createAnalogModeButton(String name, String text, int _x, int _y, int _w, int _h, PFont _font, int _fontSize, color _bg, color _textColor) { @@ -212,7 +212,7 @@ class AnalogReadBar{ private AnalogCapableBoard analogBoard; - AnalogReadBar(PApplet _parent, int _analogInputPin, int _x, int _y, int _w, int _h) { // channel number, x/y location, height, width + AnalogReadBar(PApplet _parentApplet, int _analogInputPin, int _x, int _y, int _w, int _h) { // channel number, x/y location, height, width analogInputPin = _analogInputPin; int digitalPinNum = 0; @@ -236,7 +236,7 @@ class AnalogReadBar{ h = _h; numSeconds = 20; - plot = new GPlot(_parent); + plot = new GPlot(_parentApplet); plot.setPos(x + 36 + 4, y); plot.setDim(w - 36 - 4, h); plot.setMar(0f, 0f, 0f, 0f); diff --git a/OpenBCI_GUI/W_BandPower.pde b/OpenBCI_GUI/W_BandPower.pde index 5a888ce70..96e380bb8 100644 --- a/OpenBCI_GUI/W_BandPower.pde +++ b/OpenBCI_GUI/W_BandPower.pde @@ -40,14 +40,14 @@ class W_BandPower extends Widget { int[] autoCleanTimers; boolean[] previousThresholdCrossed; - W_BandPower(PApplet _parent) { - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_BandPower(String _widgetName) { + super(_widgetName); autoCleanTimers = new int[currentBoard.getNumEXGChannels()]; previousThresholdCrossed = new boolean[currentBoard.getNumEXGChannels()]; //Add channel select dropdown to this widget - bpChanSelect = new ExGChannelSelect(pApplet, x, y, w, navH); + bpChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); bpChanSelect.activateAllButtons(); cp5ElementsToCheck.addAll(bpChanSelect.getCp5ElementsForOverlapCheck()); @@ -65,7 +65,7 @@ class W_BandPower extends Widget { addDropdown("bandPowerDataFilteringDropdown", "Filtered?", filteredEnum.getEnumStringsAsList(), filteredEnum.getIndex()); // Setup for the BandPower plot - bp_plot = new GPlot(_parent, x, y-navHeight, w, h+navHeight); + bp_plot = new GPlot(ourApplet, x, y-navHeight, w, h+navHeight); // bp_plot.setPos(x, y+navHeight); bp_plot.setDim(w, h); bp_plot.setLogScale("y"); @@ -107,7 +107,7 @@ class W_BandPower extends Widget { } public void update() { - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); // If enabled, automatically turn channels on or off in ExGChannelSelect for this widget autoCleanByEnableDisableChannels(); @@ -135,7 +135,7 @@ class W_BandPower extends Widget { } public void draw() { - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); pushStyle(); //remember to refer to x,y,w,h which are the positioning variables of the Widget class @@ -158,15 +158,15 @@ class W_BandPower extends Widget { } public void screenResized() { - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); flexGPlotSizeAndPosition(); - bpChanSelect.screenResized(pApplet); + bpChanSelect.screenResized(ourApplet); } public void mousePressed() { - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); bpChanSelect.mousePressed(this.dropdownIsActive); //Calls channel select mousePressed and checks if clicked } diff --git a/OpenBCI_GUI/W_CytonImpedance.pde b/OpenBCI_GUI/W_CytonImpedance.pde index f6df10ad1..f5e8117fc 100644 --- a/OpenBCI_GUI/W_CytonImpedance.pde +++ b/OpenBCI_GUI/W_CytonImpedance.pde @@ -56,8 +56,8 @@ class W_CytonImpedance extends Widget { private int thresholdTFWidth = 60; //Hard-code this value since there are deep errors with controlp5.textfield.setSize() and creating new graphics in this class - RW 12/13/2021 - W_CytonImpedance(PApplet _parent){ - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_CytonImpedance(String _widgetName){ + super(_widgetName); cytonBoard = (BoardCyton) currentBoard; @@ -101,7 +101,7 @@ class W_CytonImpedance extends Widget { } public void update(){ - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); if (is_railed == null) { return; @@ -131,7 +131,7 @@ class W_CytonImpedance extends Widget { } public void draw(){ - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); dataGrid.draw(); @@ -176,7 +176,7 @@ class W_CytonImpedance extends Widget { } public void screenResized(){ - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); int overrideDropdownWidth = 64; cp5_widget.get(ScrollableList.class, "CytonImpedance_MasterCheckInterval").setWidth(overrideDropdownWidth); @@ -184,8 +184,8 @@ class W_CytonImpedance extends Widget { //**IMPORTANT FOR CP5**// //This makes the cp5 objects within the widget scale properly - imp_buttons_cp5.setGraphics(pApplet, 0, 0); - threshold_ui_cp5.setGraphics(pApplet, 0, 0); + imp_buttons_cp5.setGraphics(ourApplet, 0, 0); + threshold_ui_cp5.setGraphics(ourApplet, 0, 0); cytonResetAllChannels.setPosition((int)(x0 + 1), (int)(y0 + navHeight + 1)); cytonImpedanceMasterCheck.setPosition((int)(x0 + 1 + padding_3 + 90), (int)(y0 + navHeight + 1)); @@ -242,11 +242,11 @@ class W_CytonImpedance extends Widget { } public void mousePressed(){ - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); } public void mouseReleased(){ - super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) + super.mouseReleased(); } private void initCytonImpedanceMap() { diff --git a/OpenBCI_GUI/W_DigitalRead.pde b/OpenBCI_GUI/W_DigitalRead.pde index d3990aea9..7485b474c 100644 --- a/OpenBCI_GUI/W_DigitalRead.pde +++ b/OpenBCI_GUI/W_DigitalRead.pde @@ -22,8 +22,8 @@ class W_DigitalRead extends Widget { private DigitalCapableBoard digitalBoard; - W_DigitalRead(PApplet _parent) { - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_DigitalRead(String _widgetName) { + super(_widgetName); digitalBoard = (DigitalCapableBoard)currentBoard; @@ -54,7 +54,7 @@ class W_DigitalRead extends Widget { } else { digitalPin = 18; } - DigitalReadDot tempDot = new DigitalReadDot(_parent, digitalPin, digitalReaddotX, digitalReaddotY, int(dot_w), digitalReaddotHeight, dot_padding); + DigitalReadDot tempDot = new DigitalReadDot(ourApplet, digitalPin, digitalReaddotX, digitalReaddotY, int(dot_w), digitalReaddotHeight, dot_padding); digitalReadDots[i] = tempDot; } @@ -66,7 +66,7 @@ class W_DigitalRead extends Widget { } public void update() { - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); if (currentBoard instanceof DataSourcePlayback) { if (((DataSourcePlayback)currentBoard) instanceof DigitalCapableBoard @@ -93,7 +93,7 @@ class W_DigitalRead extends Widget { } public void draw() { - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); //draw channel bars if (digitalBoard.isDigitalActive()) { @@ -104,7 +104,7 @@ class W_DigitalRead extends Widget { } public void screenResized() { - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); if (w > h) { digitalReaddotHeight = int(h/(numDigitalReadDots+1)); @@ -130,11 +130,11 @@ class W_DigitalRead extends Widget { } public void mousePressed() { - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); } public void mouseReleased() { - super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) + super.mouseReleased(); } private void createDigitalModeButton(String name, String text, int _x, int _y, int _w, int _h, PFont _font, int _fontSize, color _bg, color _textColor) { @@ -205,7 +205,7 @@ class DigitalReadDot{ DigitalCapableBoard digitalBoard; - DigitalReadDot(PApplet _parent, int _digitalInputPin, int _x, int _y, int _w, int _h, int _padding) { // channel number, x/y location, height, width + DigitalReadDot(PApplet _parentApplet, int _digitalInputPin, int _x, int _y, int _w, int _h, int _padding) { // channel number, x/y location, height, width digitalBoard = (DigitalCapableBoard)currentBoard; diff --git a/OpenBCI_GUI/W_EMG.pde b/OpenBCI_GUI/W_EMG.pde index 905898ef4..97ab38e9a 100644 --- a/OpenBCI_GUI/W_EMG.pde +++ b/OpenBCI_GUI/W_EMG.pde @@ -14,8 +14,6 @@ //////////////////////////////////////////////////////////////////////////////// class W_emg extends Widget { - PApplet parent; - private ControlP5 emgCp5; private Button emgSettingsButton; private final int EMG_SETTINGS_BUTTON_WIDTH = 125; @@ -23,14 +21,13 @@ class W_emg extends Widget { public ExGChannelSelect emgChannelSelect; - W_emg (PApplet _parent) { - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) - parent = _parent; + W_emg (String _widgetName) { + super(_widgetName); cp5ElementsToCheck = new ArrayList(); //Add channel select dropdown to this widget - emgChannelSelect = new ExGChannelSelect(pApplet, x, y, w, navH); + emgChannelSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); emgChannelSelect.activateAllButtons(); cp5ElementsToCheck.addAll(emgChannelSelect.getCp5ElementsForOverlapCheck()); @@ -44,7 +41,7 @@ class W_emg extends Widget { } public void update() { - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); lockElementsOnOverlapCheck(cp5ElementsToCheck); //Update channel checkboxes and active channels @@ -60,7 +57,7 @@ class W_emg extends Widget { } public void draw() { - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); drawEmgVisualizations(); @@ -71,14 +68,14 @@ class W_emg extends Widget { } public void screenResized() { - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); emgCp5.setGraphics(ourApplet, 0, 0); emgSettingsButton.setPosition(x0 + w - EMG_SETTINGS_BUTTON_WIDTH - 2, y0 + navH + 1); - emgChannelSelect.screenResized(pApplet); + emgChannelSelect.screenResized(ourApplet); } public void mousePressed() { - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); //Calls channel select mousePressed and checks if clicked emgChannelSelect.mousePressed(this.dropdownIsActive); } diff --git a/OpenBCI_GUI/W_EMGJoystick.pde b/OpenBCI_GUI/W_EMGJoystick.pde index dabef103f..4f645dd7a 100644 --- a/OpenBCI_GUI/W_EMGJoystick.pde +++ b/OpenBCI_GUI/W_EMGJoystick.pde @@ -71,8 +71,8 @@ class W_EMGJoystick extends Widget { private PImage yPositiveInputLabelImage = loadImage("EMG_Joystick/UP_100x100.png"); private PImage yNegativeInputLabelImage = loadImage("EMG_Joystick/DOWN_100x100.png"); - W_EMGJoystick(PApplet _parent){ - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_EMGJoystick(String _widgetName) { + super(_widgetName); emgCp5 = new ControlP5(ourApplet); emgCp5.setGraphics(ourApplet, 0,0); @@ -102,12 +102,12 @@ class W_EMGJoystick extends Widget { } public void update(){ - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); lockElementsOnOverlapCheck(cp5ElementsToCheck); } public void draw(){ - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); drawJoystickXYGraph(); @@ -122,7 +122,7 @@ class W_EMGJoystick extends Widget { } public void screenResized(){ - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); emgCp5.setGraphics(ourApplet, 0, 0); emgSettingsButton.setPosition(x0 + 1, y0 + navH + 1); diff --git a/OpenBCI_GUI/W_FFT.pde b/OpenBCI_GUI/W_FFT.pde index d475946d7..32f72e836 100644 --- a/OpenBCI_GUI/W_FFT.pde +++ b/OpenBCI_GUI/W_FFT.pde @@ -28,10 +28,10 @@ class W_fft extends Widget { List cp5ElementsToCheck = new ArrayList(); - W_fft(PApplet _parent){ - super(_parent); + W_fft(String _widgetName) { + super(_widgetName); - fftChanSelect = new ExGChannelSelect(pApplet, x, y, w, navH); + fftChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); fftChanSelect.activateAllButtons(); cp5ElementsToCheck.addAll(fftChanSelect.getCp5ElementsForOverlapCheck()); @@ -45,12 +45,12 @@ class W_fft extends Widget { addDropdown("fftFilteringDropdown", "Filters?", filteredEnum.getEnumStringsAsList(), filteredEnum.getIndex()); fftGplotPoints = new GPointsArray[globalChannelCount]; - initializeFFTPlot(_parent); + initializeFFTPlot(); } - void initializeFFTPlot(PApplet _parent) { + void initializeFFTPlot() { //setup GPlot for FFT - fftPlot = new GPlot(_parent, x, y-navHeight, w, h+navHeight); + fftPlot = new GPlot(ourApplet, x, y-navHeight, w, h+navHeight); fftPlot.setAllFontProperties("Arial", 0, 14); fftPlot.getXAxis().setAxisLabelText("Frequency (Hz)"); fftPlot.getYAxis().setAxisLabelText("Amplitude (uV)"); @@ -90,7 +90,7 @@ class W_fft extends Widget { void update(){ - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); float sampleRate = currentBoard.getSampleRate(); int fftPointCount = getNumFFTPoints(); @@ -117,7 +117,7 @@ class W_fft extends Widget { } void draw(){ - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); //remember to refer to x,y,w,h which are the positioning variables of the Widget class pushStyle(); @@ -150,20 +150,20 @@ class W_fft extends Widget { } void screenResized(){ - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); flexGPlotSizeAndPosition(); - fftChanSelect.screenResized(pApplet); + fftChanSelect.screenResized(ourApplet); } void mousePressed(){ - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); fftChanSelect.mousePressed(this.dropdownIsActive); //Calls channel select mousePressed and checks if clicked } void mouseReleased(){ - super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) + super.mouseReleased(); } void flexGPlotSizeAndPosition() { diff --git a/OpenBCI_GUI/W_Focus.pde b/OpenBCI_GUI/W_Focus.pde index 678b5357d..eb2d164ba 100644 --- a/OpenBCI_GUI/W_Focus.pde +++ b/OpenBCI_GUI/W_Focus.pde @@ -64,11 +64,11 @@ class W_Focus extends Widget { List cp5ElementsToCheck = new ArrayList(); - W_Focus(PApplet _parent) { - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_Focus(String _widgetName) { + super(_widgetName); //Add channel select dropdown to this widget - focusChanSelect = new ExGChannelSelect(pApplet, x, y, w, navH); + focusChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); focusChanSelect.activateAllButtons(); cp5ElementsToCheck.addAll(focusChanSelect.getCp5ElementsForOverlapCheck()); @@ -103,13 +103,13 @@ class W_Focus extends Widget { //create our focus graph updateGraphDims(); - focusBar = new FifoChannelBar(_parent, "Metric Value", xLimit.getValue(), focusBarHardYAxisLimit, graphX, graphY, graphW, graphH, ACCEL_X_COLOR, FocusXLim.TWENTY.getValue()); + focusBar = new FifoChannelBar(ourApplet, "Metric Value", xLimit.getValue(), focusBarHardYAxisLimit, graphX, graphY, graphW, graphH, ACCEL_X_COLOR, FocusXLim.TWENTY.getValue()); initBrainFlowMetric(); } public void update() { - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); //Update channel checkboxes and active channels focusChanSelect.update(x, y, w); @@ -129,7 +129,7 @@ class W_Focus extends Widget { } public void draw() { - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); //remember to refer to x,y,w,h which are the positioning variables of the Widget class //Draw data table @@ -159,7 +159,7 @@ class W_Focus extends Widget { } public void screenResized() { - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); resizeTable(); @@ -168,7 +168,7 @@ class W_Focus extends Widget { updateGraphDims(); focusBar.screenResized(graphX, graphY, graphW, graphH); - focusChanSelect.screenResized(pApplet); + focusChanSelect.screenResized(ourApplet); //Custom resize these dropdowns due to longer text strings as options cp5_widget.get(ScrollableList.class, "focusMetricDropdown").setWidth(METRIC_DROPDOWN_W); @@ -179,7 +179,7 @@ class W_Focus extends Widget { } void mousePressed() { - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); focusChanSelect.mousePressed(this.dropdownIsActive); //Calls channel select mousePressed and checks if clicked } diff --git a/OpenBCI_GUI/W_GanglionImpedance.pde b/OpenBCI_GUI/W_GanglionImpedance.pde index b073e0a7a..f27ddc19c 100644 --- a/OpenBCI_GUI/W_GanglionImpedance.pde +++ b/OpenBCI_GUI/W_GanglionImpedance.pde @@ -15,18 +15,18 @@ class W_GanglionImpedance extends Widget { Button startStopCheck; int padding = 24; - W_GanglionImpedance(PApplet _parent){ - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_GanglionImpedance(String _widgetName) { + super(_widgetName); createStartStopCheck("startStopCheck", "Start Impedance Check", x + padding, y + padding, 200, navHeight, p4, 14, colorNotPressed, OPENBCI_DARKBLUE); } void update(){ - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); } void draw(){ - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); //remember to refer to x,y,w,h which are the positioning variables of the Widget class pushStyle(); @@ -80,18 +80,10 @@ class W_GanglionImpedance extends Widget { } void screenResized(){ - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); startStopCheck.setPosition(x + padding, y + padding); } - void mousePressed(){ - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) - } - - void mouseReleased(){ - super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) - } - private void createStartStopCheck(String name, String text, int _x, int _y, int _w, int _h, PFont _font, int _fontSize, color _bg, color _textColor) { startStopCheck = createButton(cp5_widget, name, text, _x, _y, _w, _h, _font, _fontSize, _bg, _textColor); startStopCheck.onRelease(new CallbackListener() { diff --git a/OpenBCI_GUI/W_HeadPlot.pde b/OpenBCI_GUI/W_HeadPlot.pde index c34698414..2291493fa 100644 --- a/OpenBCI_GUI/W_HeadPlot.pde +++ b/OpenBCI_GUI/W_HeadPlot.pde @@ -28,8 +28,8 @@ class W_HeadPlot extends Widget { private final float DEFAULT_VERTICAL_SCALE_UV = 200f; //this defines the Y-scale on the montage plots...this is the vertical space between traces - W_HeadPlot(PApplet _parent){ - super(_parent); + W_HeadPlot(String _widgetName) { + super(_widgetName); addDropdown("headPlotIntensityDropdown", "Intensity", headPlotIntensity.getEnumStringsAsList(), headPlotIntensity.getIndex()); addDropdown("headPlotPolarityDropdown", "Polarity", headPlotPolarity.getEnumStringsAsList(), headPlotPolarity.getIndex()); @@ -51,12 +51,12 @@ class W_HeadPlot extends Widget { } public void draw(){ - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); headPlot.draw(); //draw the actual headplot } public void screenResized(){ - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); headPlot.hp_x = x; headPlot.hp_y = y; headPlot.hp_w = w; @@ -68,17 +68,17 @@ class W_HeadPlot extends Widget { } public void mousePressed(){ - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); headPlot.mousePressed(); } public void mouseReleased(){ - super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) + super.mouseReleased(); headPlot.mouseReleased(); } public void mouseDragged(){ - super.mouseDragged(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) + super.mouseDragged(); headPlot.mouseDragged(); } diff --git a/OpenBCI_GUI/W_Marker.pde b/OpenBCI_GUI/W_Marker.pde index 7eaaedb08..ff1d8abb9 100644 --- a/OpenBCI_GUI/W_Marker.pde +++ b/OpenBCI_GUI/W_Marker.pde @@ -45,8 +45,8 @@ class W_Marker extends Widget { private MarkerVertScale markerVertScale = MarkerVertScale.EIGHT; private MarkerWindow markerWindow = MarkerWindow.FIVE; - W_Marker(PApplet _parent){ - super(_parent); + W_Marker(String _widgetName) { + super(_widgetName); //Instantiate local cp5 for this box. This allows extra control of drawing cp5 elements specifically inside this class. localCP5 = new ControlP5(ourApplet); @@ -59,7 +59,7 @@ class W_Marker extends Widget { addDropdown("markerWindowDropdown", "Window", markerWindow.getEnumStringsAsList(), markerWindow.getIndex()); updateGraphDims(); - markerBar = new MarkerBar(_parent, MAX_NUMBER_OF_MARKER_BUTTONS, markerWindow.getValue(), markerVertScale.getValue(), graphX, graphY, graphW, graphH); + markerBar = new MarkerBar(ourApplet, MAX_NUMBER_OF_MARKER_BUTTONS, markerWindow.getValue(), markerVertScale.getValue(), graphX, graphY, graphW, graphH); grid = new Grid(MARKER_UI_GRID_ROWS, MARKER_UI_GRID_COLUMNS, MARKER_UI_GRID_CELL_HEIGHT); grid.setDrawTableBorder(false); @@ -383,7 +383,7 @@ class MarkerBar { private float autoscaleMax; private int previousMillis = 0; - MarkerBar(PApplet _parent, int _yAxisMax, int markerWindow, float yLimit, int _x, int _y, int _w, int _h) { //channel number, x/y location, height, width + MarkerBar(PApplet _parentApplet, int _yAxisMax, int markerWindow, float yLimit, int _x, int _y, int _w, int _h) { //channel number, x/y location, height, width yAxisMax = _yAxisMax; numSeconds = markerWindow; @@ -395,7 +395,7 @@ class MarkerBar { w = _w; h = _h; - plot = new GPlot(_parent); + plot = new GPlot(_parentApplet); plot.setPos(x + 36 + 4, y); //match marker plot position with Time Series plot.setDim(w - 36 - 4, h); plot.setMar(0f, 0f, 0f, 0f); diff --git a/OpenBCI_GUI/W_PacketLoss.pde b/OpenBCI_GUI/W_PacketLoss.pde index 9b4882685..2267926d4 100644 --- a/OpenBCI_GUI/W_PacketLoss.pde +++ b/OpenBCI_GUI/W_PacketLoss.pde @@ -37,8 +37,8 @@ class W_PacketLoss extends Widget { private CalculationWindowSize tableWindowSize = CalculationWindowSize.SECONDS10; - W_PacketLoss(PApplet _parent){ - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_PacketLoss(String _widgetName) { + super(_widgetName); dataGrid = new Grid(5/*numRows*/, 4/*numCols*/, CELL_HEIGHT); packetLossTracker = ((Board)currentBoard).getPacketLossTracker(); @@ -102,7 +102,7 @@ class W_PacketLoss extends Widget { } public void update(){ - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); lastMillisPacketRecord = packetLossTracker.getCumulativePacketRecordForLast(tableWindowSize.getMilliseconds()); @@ -129,7 +129,7 @@ class W_PacketLoss extends Widget { } public void draw(){ - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); pushStyle(); fill(OPENBCI_DARKBLUE); @@ -142,17 +142,17 @@ class W_PacketLoss extends Widget { } public void screenResized(){ - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); resizeGrid(); } public void mousePressed(){ - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); } public void mouseReleased(){ - super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) + super.mouseReleased(); } diff --git a/OpenBCI_GUI/W_Playback.pde b/OpenBCI_GUI/W_Playback.pde index b200dffca..2245eaac5 100644 --- a/OpenBCI_GUI/W_Playback.pde +++ b/OpenBCI_GUI/W_Playback.pde @@ -21,10 +21,10 @@ class W_playback extends Widget { private boolean menuHasUpdated = false; - W_playback(PApplet _parent) { - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_playback(String _widgetName) { + super(_widgetName); - cp5_playback = new ControlP5(pApplet); + cp5_playback = new ControlP5(ourApplet); cp5_playback.setGraphics(ourApplet, 0,0); cp5_playback.setAutoDraw(false); @@ -34,7 +34,7 @@ class W_playback extends Widget { } void update() { - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); if (!menuHasUpdated) { refreshPlaybackList(); menuHasUpdated = true; @@ -56,7 +56,7 @@ class W_playback extends Widget { } void draw() { - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); //x,y,w,h are the positioning variables of the Widget class pushStyle(); @@ -77,11 +77,11 @@ class W_playback extends Widget { } //end draw loop void screenResized() { - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); //**IMPORTANT FOR CP5**// //This makes the cp5 objects within the widget scale properly - cp5_playback.setGraphics(pApplet, 0, 0); + cp5_playback.setGraphics(ourApplet, 0, 0); //Resize and position cp5 objects within this widget selectPlaybackFileButton.setPosition(x + w - selectPlaybackFileButton.getWidth() - 2, y - navHeight + 2); diff --git a/OpenBCI_GUI/W_PulseSensor.pde b/OpenBCI_GUI/W_PulseSensor.pde index 1881847f5..db0045247 100644 --- a/OpenBCI_GUI/W_PulseSensor.pde +++ b/OpenBCI_GUI/W_PulseSensor.pde @@ -62,8 +62,8 @@ class W_PulseSensor extends Widget { private AnalogCapableBoard analogBoard; - W_PulseSensor(PApplet _parent){ - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_PulseSensor(String _widgetName) { + super(_widgetName); analogBoard = (AnalogCapableBoard)currentBoard; @@ -80,7 +80,7 @@ class W_PulseSensor extends Widget { } public void update(){ - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); if(currentBoard instanceof DataSourcePlayback) { if (((DataSourcePlayback)currentBoard) instanceof AnalogCapableBoard @@ -104,7 +104,7 @@ class W_PulseSensor extends Widget { } public void draw(){ - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); //remember to refer to x,y,w,h which are the positioning variables of the Widget class pushStyle(); @@ -128,7 +128,7 @@ class W_PulseSensor extends Widget { } public void screenResized(){ - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); setPulseWidgetVariables(); analogModeButton.setPosition((int)(x0 + 1), (int)(y0 + navHeight + 1)); diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index e899c4b47..071517611 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -48,12 +48,12 @@ class W_Spectrogram extends Widget { private SpectrogramWindowSize windowSize = SpectrogramWindowSize.ONE_MINUTE; private FFTLogLin logLin = FFTLogLin.LIN; - W_Spectrogram(PApplet _parent) { - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_Spectrogram(String _widgetName) { + super(_widgetName); //Add channel select dropdown to this widget - spectChanSelectTop = new DualExGChannelSelect(pApplet, x, y, w, navH, true); - spectChanSelectBot = new DualExGChannelSelect(pApplet, x, y + navH, w, navH, false); + spectChanSelectTop = new DualExGChannelSelect(ourApplet, x, y, w, navH, true); + spectChanSelectBot = new DualExGChannelSelect(ourApplet, x, y + navH, w, navH, false); activateDefaultChannels(); cp5ElementsToCheck.addAll(spectChanSelectTop.getCp5ElementsForOverlapCheck()); @@ -84,7 +84,7 @@ class W_Spectrogram extends Widget { } void update(){ - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); //Update channel checkboxes, active channels, and position spectChanSelectTop.update(x, y, w); @@ -131,7 +131,7 @@ class W_Spectrogram extends Widget { } public void draw(){ - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); //put your code here... //remember to refer to x,y,w,h which are the positioning variables of the Widget class @@ -219,10 +219,10 @@ class W_Spectrogram extends Widget { } public void screenResized(){ - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); - spectChanSelectTop.screenResized(pApplet); - spectChanSelectBot.screenResized(pApplet); + spectChanSelectTop.screenResized(ourApplet); + spectChanSelectBot.screenResized(ourApplet); graphX = x + paddingLeft; graphY = y + paddingTop; graphW = w - paddingRight - paddingLeft; @@ -230,14 +230,14 @@ class W_Spectrogram extends Widget { } void mousePressed(){ - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); spectChanSelectTop.mousePressed(this.dropdownIsActive); //Calls channel select mousePressed and checks if clicked spectChanSelectBot.mousePressed(this.dropdownIsActive); } void mouseReleased(){ - super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) + super.mouseReleased(); } diff --git a/OpenBCI_GUI/W_Template.pde b/OpenBCI_GUI/W_Template.pde index 2d55fc3d4..ce20a62bf 100644 --- a/OpenBCI_GUI/W_Template.pde +++ b/OpenBCI_GUI/W_Template.pde @@ -17,8 +17,8 @@ class W_template extends Widget { ControlP5 localCP5; Button widgetTemplateButton; - W_template(PApplet _parent){ - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_template(String _widgetName) { + super(_widgetName); //This is the protocol for setting up dropdowns. //Note that these 3 dropdowns correspond to the 3 global functions below @@ -38,13 +38,13 @@ class W_template extends Widget { } public void update(){ - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); //put your code here... } public void draw(){ - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); //remember to refer to x,y,w,h which are the positioning variables of the Widget class @@ -53,7 +53,7 @@ class W_template extends Widget { } public void screenResized(){ - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); //Very important to allow users to interact with objects after app resize localCP5.setGraphics(ourApplet, 0, 0); @@ -64,14 +64,14 @@ class W_template extends Widget { } public void mousePressed(){ - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); //Since GUI v5, these methods should not really be used. //Instead, use ControlP5 objects and callbacks. //Example: createWidgetTemplateButton() found below } public void mouseReleased(){ - super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) + super.mouseReleased(); //Since GUI v5, these methods should not really be used. } diff --git a/OpenBCI_GUI/W_TimeSeries.pde b/OpenBCI_GUI/W_TimeSeries.pde index 7b32f8633..fc22a6132 100644 --- a/OpenBCI_GUI/W_TimeSeries.pde +++ b/OpenBCI_GUI/W_TimeSeries.pde @@ -51,14 +51,14 @@ class W_timeSeries extends Widget { List cp5ElementsToCheck = new ArrayList(); - W_timeSeries(PApplet _parent) { - super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) + W_timeSeries(String _widgetName) { + super(_widgetName); - tscp5 = new ControlP5(_parent); - tscp5.setGraphics(_parent, 0, 0); + tscp5 = new ControlP5(ourApplet); + tscp5.setGraphics(ourApplet, 0, 0); tscp5.setAutoDraw(false); - tsChanSelect = new ExGChannelSelect(pApplet, x, y, w, navH); + tsChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); //Activate all channels in channelSelect by default for this widget tsChanSelect.activateAllButtons(); @@ -113,7 +113,7 @@ class W_timeSeries extends Widget { //create our channel bars and populate our channelBars array! for(int i = 0; i < numChannelBars; i++) { int channelBarY = int(ts_y) + i*(channelBarHeight); //iterate through bar locations - ChannelBar tempBar = new ChannelBar(_parent, i, int(ts_x), channelBarY, int(ts_w), channelBarHeight, expand_default, expand_hover, expand_active, contract_default, contract_hover, contract_active); + ChannelBar tempBar = new ChannelBar(ourApplet, i, int(ts_x), channelBarY, int(ts_w), channelBarHeight, expand_default, expand_hover, expand_active, contract_default, contract_hover, contract_active); channelBars[i] = tempBar; } @@ -125,14 +125,14 @@ class W_timeSeries extends Widget { if (currentBoard instanceof ADS1299SettingsBoard) { hwSettingsButton = createHSCButton("HardwareSettings", "Hardware Settings", (int)(x0 + 80), (int)(y0 + navHeight + 1), 120, navHeight - 3); cp5ElementsToCheck.add((controlP5.Controller)hwSettingsButton); - adsSettingsController = new ADS1299SettingsController(_parent, tsChanSelect.getActiveChannels(), x_hsc, y_hsc, w_hsc, h_hsc, channelBarHeight); + adsSettingsController = new ADS1299SettingsController(ourApplet, tsChanSelect.getActiveChannels(), x_hsc, y_hsc, w_hsc, h_hsc, channelBarHeight); } setVerticalScale(yLimit.getIndex()); } void update() { - super.update(); //calls the parent update() method of Widget (DON'T REMOVE) + super.update(); // offset based on whether channel select or hardware settings are open or not int chanSelectOffset = tsChanSelect.isVisible() ? tsChanSelect.getHeight() : 0; @@ -178,7 +178,7 @@ class W_timeSeries extends Widget { } void draw() { - super.draw(); //calls the parent draw() method of Widget (DON'T REMOVE) + super.draw(); //remember to refer to x,y,w,h which are the positioning variables of the Widget class //draw channel bars @@ -206,12 +206,12 @@ class W_timeSeries extends Widget { } void screenResized() { - super.screenResized(); //calls the parent screenResized() method of Widget (DON'T REMOVE) + super.screenResized(); //Very important to allow users to interact with objects after app resize tscp5.setGraphics(ourApplet, 0,0); - tsChanSelect.screenResized(pApplet); + tsChanSelect.screenResized(ourApplet); xF = float(x); //float(int( ... is a shortcut for rounding the float down... so that it doesn't creep into the 1px margin yF = float(y); @@ -262,7 +262,7 @@ class W_timeSeries extends Widget { } void mousePressed() { - super.mousePressed(); //calls the parent mousePressed() method of Widget (DON'T REMOVE) + super.mousePressed(); tsChanSelect.mousePressed(this.dropdownIsActive); //Calls channel select mousePressed and checks if clicked for(int i = 0; i < tsChanSelect.getActiveChannels().size(); i++) { @@ -272,7 +272,7 @@ class W_timeSeries extends Widget { } void mouseReleased() { - super.mouseReleased(); //calls the parent mouseReleased() method of Widget (DON'T REMOVE) + super.mouseReleased(); for(int i = 0; i < tsChanSelect.getActiveChannels().size(); i++) { int activeChannel = tsChanSelect.getActiveChannels().get(i); @@ -415,7 +415,7 @@ class ChannelBar { boolean drawVoltageValue; - ChannelBar(PApplet _parent, int _channelIndex, int _x, int _y, int _w, int _h, PImage expand_default, PImage expand_hover, PImage expand_active, PImage contract_default, PImage contract_hover, PImage contract_active) { + ChannelBar(PApplet _parentApplet, int _channelIndex, int _x, int _y, int _w, int _h, PImage expand_default, PImage expand_hover, PImage expand_active, PImage contract_default, PImage contract_hover, PImage contract_active) { cbCp5 = new ControlP5(ourApplet); cbCp5.setGraphics(ourApplet, x, y); @@ -438,7 +438,7 @@ class ChannelBar { yAxisUpperLim = 200; yAxisLowerLim = -200; numSeconds = 5; - plot = new GPlot(_parent); + plot = new GPlot(_parentApplet); plot.setPos(x + uiSpaceWidth, y); plot.setDim(w - uiSpaceWidth, h); plot.setMar(0f, 0f, 0f, 0f); @@ -712,8 +712,8 @@ class ChannelBar { onOffButton.setPosition(x + 6, y + int(h/2) - int(onOff_diameter/2)); } - public void updateCP5(PApplet _parent) { - cbCp5.setGraphics(_parent, 0, 0); + public void updateCP5(PApplet _parentApplet) { + cbCp5.setGraphics(_parentApplet, 0, 0); } private boolean isBottomChannel() { diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index ab1867514..0b69181c6 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -15,8 +15,6 @@ interface IndexingInterface { class Widget { - protected PApplet pApplet; - protected int x0, y0, w0, h0; //true x,y,w,h of container protected int x, y, w, h; //adjusted x,y,w,h of white space `blank rectangle` under the nav... @@ -41,9 +39,9 @@ class Widget { protected int dropdownWidth = 64; private boolean initialResize = false; //used to properly resize the widgetSelector when loading default settings - Widget(PApplet _parent){ - pApplet = _parent; - cp5_widget = new ControlP5(pApplet); + Widget(String _title) { + widgetTitle = _title; + cp5_widget = new ControlP5(ourApplet); cp5_widget.setAutoDraw(false); //this prevents the cp5 object from drawing automatically (if it is set to true it will be drawn last, on top of all other GUI stuff... not good) dropdowns = new ArrayList(); @@ -217,10 +215,6 @@ class Widget { mapToCurrentContainer(); } - public void setTitle(String _widgetTitle){ - widgetTitle = _widgetTitle; - } - public void setContainer(int _currentContainer){ currentContainer = _currentContainer; mapToCurrentContainer(); @@ -256,7 +250,7 @@ class Widget { h = h0 - navH*2; //This line resets the origin for all cp5 elements under "cp5_widget" when the screen is resized, otherwise there will be drawing errors - cp5_widget.setGraphics(pApplet, 0, 0); + cp5_widget.setGraphics(ourApplet, 0, 0); if (cp5_widget.getController("WidgetSelector") != null) { resizeWidgetSelector(); diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index 43ebf95de..7a49784db 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -8,7 +8,7 @@ - use the WidgetTemplate.pde file as a starting point for creating new widgets (also check out W_timeSeries.pde, W_fft.pde, and W_HeadPlot.pde) */ -// MAKE YOUR WIDGET GLOBALLY +// MAKE YOUR WIDGET GLOBAL HERE W_timeSeries w_timeSeries; W_fft w_fft; W_BandPower w_bandPower; @@ -16,7 +16,7 @@ W_Accelerometer w_accelerometer; W_CytonImpedance w_cytonImpedance; W_GanglionImpedance w_ganglionImpedance; W_HeadPlot w_headPlot; -W_template w_template1; +W_template w_template; W_emg w_emg; W_PulseSensor w_pulseSensor; W_AnalogRead w_analogRead; @@ -45,14 +45,14 @@ class WidgetManager{ public boolean isWMInitialized = false; private boolean visible = true; - WidgetManager(PApplet _this){ + WidgetManager() { widgets = new ArrayList(); widgetOptions = new ArrayList(); isWMInitialized = false; //DO NOT re-order the functions below setupLayouts(); - setupWidgets(_this); + setupWidgets(); setupWidgetSelectorDropdowns(); if(globalChannelCount == 4 && eegDataSource == DATASOURCE_GANGLION) { @@ -72,100 +72,82 @@ class WidgetManager{ isWMInitialized = true; } - void setupWidgets(PApplet _this) { + void setupWidgets() { // println(" setupWidgets start -- " + millis()); - w_timeSeries = new W_timeSeries(_this); - w_timeSeries.setTitle("Time Series"); + w_timeSeries = new W_timeSeries("Time Series"); widgets.add(w_timeSeries); - w_fft = new W_fft(_this); - w_fft.setTitle("FFT Plot"); + w_fft = new W_fft("FFT Plot"); widgets.add(w_fft); boolean showAccelerometerWidget = currentBoard instanceof AccelerometerCapableBoard; if (showAccelerometerWidget) { - w_accelerometer = new W_Accelerometer(_this); - w_accelerometer.setTitle("Accelerometer"); + w_accelerometer = new W_Accelerometer("Accelerometer"); widgets.add(w_accelerometer); } if (currentBoard instanceof BoardCyton) { - w_cytonImpedance = new W_CytonImpedance(_this); - w_cytonImpedance.setTitle("Cyton Signal"); + w_cytonImpedance = new W_CytonImpedance("Cyton Signal"); widgets.add(w_cytonImpedance); } if (currentBoard instanceof DataSourcePlayback && w_playback == null) { - w_playback = new W_playback(_this); - w_playback.setTitle("Playback History"); + w_playback = new W_playback("Playback History"); widgets.add(w_playback); } //only instantiate this widget if you are using a Ganglion board for live streaming if(globalChannelCount == 4 && currentBoard instanceof BoardGanglion){ //If using Ganglion, this is Widget_3 - w_ganglionImpedance = new W_GanglionImpedance(_this); - w_ganglionImpedance.setTitle("Ganglion Signal"); + w_ganglionImpedance = new W_GanglionImpedance("Ganglion Signal"); widgets.add(w_ganglionImpedance); } - w_focus = new W_Focus(_this); - w_focus.setTitle("Focus"); + w_focus = new W_Focus("Focus"); widgets.add(w_focus); - w_bandPower = new W_BandPower(_this); - w_bandPower.setTitle("Band Power"); + w_bandPower = new W_BandPower("Band Power"); widgets.add(w_bandPower); - w_headPlot = new W_HeadPlot(_this); - w_headPlot.setTitle("Head Plot"); + w_headPlot = new W_HeadPlot("Head Plot"); widgets.add(w_headPlot); - w_emg = new W_emg(_this); - w_emg.setTitle("EMG"); + w_emg = new W_emg("EMG"); widgets.add(w_emg); - w_emgJoystick = new W_EMGJoystick(_this); - w_emgJoystick.setTitle("EMG Joystick"); + w_emgJoystick = new W_EMGJoystick("EMG Joystick"); widgets.add(w_emgJoystick); - w_spectrogram = new W_Spectrogram(_this); - w_spectrogram.setTitle("Spectrogram"); + w_spectrogram = new W_Spectrogram("Spectrogram"); widgets.add(w_spectrogram); if (currentBoard instanceof AnalogCapableBoard){ - w_pulseSensor = new W_PulseSensor(_this); - w_pulseSensor.setTitle("Pulse Sensor"); + w_pulseSensor = new W_PulseSensor("Pulse Sensor"); widgets.add(w_pulseSensor); } if (currentBoard instanceof DigitalCapableBoard) { - w_digitalRead = new W_DigitalRead(_this); - w_digitalRead.setTitle("Digital Read"); + w_digitalRead = new W_DigitalRead("Digital Read"); widgets.add(w_digitalRead); } if (currentBoard instanceof AnalogCapableBoard) { - w_analogRead = new W_AnalogRead(_this); - w_analogRead.setTitle("Analog Read"); + w_analogRead = new W_AnalogRead("Analog Read"); widgets.add(w_analogRead); } if (currentBoard instanceof Board) { - w_packetLoss = new W_PacketLoss(_this); - w_packetLoss.setTitle("Packet Loss"); + w_packetLoss = new W_PacketLoss("Packet Loss"); widgets.add(w_packetLoss); } - w_marker = new W_Marker(_this); - w_marker.setTitle("Marker"); + w_marker = new W_Marker("Marker"); widgets.add(w_marker); //DEVELOPERS: Here is an example widget with the essentials/structure in place - w_template1 = new W_template(_this); - w_template1.setTitle("Widget Template 1"); - widgets.add(w_template1); + w_template = new W_template("Widget Template"); + widgets.add(w_template); } @@ -362,7 +344,7 @@ class WidgetManager{ w_cytonImpedance = null; w_ganglionImpedance = null; w_headPlot = null; - w_template1 = null; + w_template = null; w_emg = null; w_pulseSensor = null; w_analogRead = null; From 43f91d604e9401c607f158d3da6b321b9c54d00f Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 10 Apr 2025 16:37:46 -0500 Subject: [PATCH 07/29] Update variable name for WidgetManager and NAV_HEIGHT --- OpenBCI_GUI/ControlPanel.pde | 2 +- OpenBCI_GUI/Interactivity.pde | 6 ++--- OpenBCI_GUI/OpenBCI_GUI.pde | 16 ++++++------ OpenBCI_GUI/SessionSettings.pde | 24 +++++++++--------- OpenBCI_GUI/TopNav.pde | 38 ++++++++++++++--------------- OpenBCI_GUI/W_Accelerometer.pde | 4 +-- OpenBCI_GUI/W_AnalogRead.pde | 4 +-- OpenBCI_GUI/W_BandPower.pde | 6 ++--- OpenBCI_GUI/W_CytonImpedance.pde | 8 +++--- OpenBCI_GUI/W_DigitalRead.pde | 4 +-- OpenBCI_GUI/W_FFT.pde | 4 +-- OpenBCI_GUI/W_Focus.pde | 10 ++++---- OpenBCI_GUI/W_GanglionImpedance.pde | 2 +- OpenBCI_GUI/W_HeadPlot.pde | 2 +- OpenBCI_GUI/W_Playback.pde | 4 +-- OpenBCI_GUI/W_PulseSensor.pde | 4 +-- OpenBCI_GUI/W_Template.pde | 2 +- OpenBCI_GUI/W_TimeSeries.pde | 4 +-- OpenBCI_GUI/Widget.pde | 22 ++++++++--------- OpenBCI_GUI/WidgetManager.pde | 19 +++------------ 20 files changed, 86 insertions(+), 99 deletions(-) diff --git a/OpenBCI_GUI/ControlPanel.pde b/OpenBCI_GUI/ControlPanel.pde index 7623fcd9e..111e4acf5 100644 --- a/OpenBCI_GUI/ControlPanel.pde +++ b/OpenBCI_GUI/ControlPanel.pde @@ -2509,7 +2509,7 @@ class InitBox { w_focus.killAuditoryFeedback(); w_marker.disposeUdpMarkerReceiver(); haltSystem(); - wm.setAllWidgetsNull(); + widgetManager.setAllWidgetsNull(); } } diff --git a/OpenBCI_GUI/Interactivity.pde b/OpenBCI_GUI/Interactivity.pde index 4abafaef5..10d579687 100644 --- a/OpenBCI_GUI/Interactivity.pde +++ b/OpenBCI_GUI/Interactivity.pde @@ -241,7 +241,7 @@ void mouseDragged() { //calling mouse dragged inly outside of Control Panel if (controlPanel.isOpen == false) { - wm.mouseDragged(); + widgetManager.mouseDragged(); } } } @@ -261,7 +261,7 @@ synchronized void mousePressed() { if (controlPanel.isOpen == false) { //was the stopButton pressed? - wm.mousePressed(); + widgetManager.mousePressed(); } } @@ -295,7 +295,7 @@ synchronized void mouseReleased() { if (systemMode >= SYSTEMMODE_POSTINIT) { // GUIWidgets_mouseReleased(); // to replace GUI_Manager version (above) soon... cdr 7/25/16 - wm.mouseReleased(); + widgetManager.mouseReleased(); } } diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde index b56effd1f..3793642cd 100644 --- a/OpenBCI_GUI/OpenBCI_GUI.pde +++ b/OpenBCI_GUI/OpenBCI_GUI.pde @@ -275,12 +275,10 @@ final int COLOR_SCHEME_ALTERNATIVE_A = 2; // int COLOR_SCHEME_ALTERNATIVE_B = 3; int colorScheme = COLOR_SCHEME_ALTERNATIVE_A; -WidgetManager wm; -boolean wmVisible = true; -CColor cp5_colors; +WidgetManager widgetManager; //Global variable for general navigation bar height -final int navHeight = 22; +final int NAV_HEIGHT = 22; ButtonHelpText buttonHelpText; @@ -743,7 +741,7 @@ void initSystem() { topNav.controlPanelCollapser.setOff(); verbosePrint("OpenBCI_GUI: initSystem: -- Init 4 -- " + millis()); - wm = new WidgetManager(); + widgetManager = new WidgetManager(); verbosePrint("OpenBCI_GUI: initSystem: -- Init 5 -- " + millis()); @@ -993,12 +991,12 @@ void systemUpdate() { // for updating data values and variables if (sessionSettings.screenHasBeenResized && sessionSettings.timeOfLastScreenResize + 500 > millis()) { ourApplet = this; //reset PApplet... topNav.screenHasBeenResized(width, height); - wm.screenResized(); + widgetManager.screenResized(); sessionSettings.screenHasBeenResized = false; } - if (wm.isWMInitialized) { - wm.update(); + if (widgetManager != null) { + widgetManager.update(); } } } @@ -1010,7 +1008,7 @@ void systemDraw() { //for drawing to the screen //background(255); //clear the screen if (systemMode >= SYSTEMMODE_POSTINIT) { - wm.draw(); + widgetManager.draw(); drawContainers(); } diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde index b85dd4e70..9ac6e74f5 100644 --- a/OpenBCI_GUI/SessionSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -437,15 +437,15 @@ class SessionSettings { int numActiveWidgets = 0; //Save what Widgets are active and respective Container number (see Containers.pde) - for (int i = 0; i < wm.widgets.size(); i++) { //increment through all widgets - if (wm.widgets.get(i).getIsActive()) { //If a widget is active... + for (int i = 0; i < widgetManager.widgets.size(); i++) { //increment through all widgets + if (widgetManager.widgets.get(i).getIsActive()) { //If a widget is active... numActiveWidgets++; //increment numActiveWidgets //println("Widget" + i + " is active"); // activeWidgets.add(i); //keep track of the active widget - int containerCountsave = wm.widgets.get(i).currentContainer; + int containerCountsave = widgetManager.widgets.get(i).currentContainer; //println("Widget " + i + " is in Container " + containerCountsave); saveWidgetSettings.setInt("Widget_"+i, containerCountsave); - } else if (!wm.widgets.get(i).getIsActive()) { //If a widget is not active... + } else if (!widgetManager.widgets.get(i).getIsActive()) { //If a widget is not active... saveWidgetSettings.remove("Widget_"+i); //remove non-active widget from JSON //println("widget"+i+" is not active"); } @@ -453,7 +453,7 @@ class SessionSettings { println("SessionSettings: " + numActiveWidgets + " active widgets saved!"); //Print what widgets are in the containers used by current layout for only the number of active widgets //for (int i = 0; i < numActiveWidgets; i++) { - //int containerCounter = wm.layouts.get(currentLayout).containerInts[i]; + //int containerCounter = widgetManager.layouts.get(currentLayout).containerInts[i]; //println("Container " + containerCounter + " is available"); //For debugging //} saveSettingsJSONData.setJSONObject(kJSONKeyWidget, saveWidgetSettings); @@ -613,16 +613,16 @@ class SessionSettings { //get the Widget/Container settings JSONObject loadWidgetSettings = loadSettingsJSONData.getJSONObject(kJSONKeyWidget); //Apply Layout directly before loading and applying widgets to containers - wm.setNewContainerLayout(currentLayout); + widgetManager.setNewContainerLayout(currentLayout); verbosePrint("LoadGUISettings: Layout " + currentLayout + " Loaded!"); numLoadedWidgets = loadWidgetSettings.size(); //int numActiveWidgets = 0; //reset the counter - for (int w = 0; w < wm.widgets.size(); w++) { //increment through all widgets - if (wm.widgets.get(w).getIsActive()) { //If a widget is active... + for (int w = 0; w < widgetManager.widgets.size(); w++) { //increment through all widgets + if (widgetManager.widgets.get(w).getIsActive()) { //If a widget is active... verbosePrint("Deactivating widget [" + w + "]"); - wm.widgets.get(w).setIsActive(false); + widgetManager.widgets.get(w).setIsActive(false); //numActiveWidgets++; //counter the number of de-activated widgets } } @@ -638,8 +638,8 @@ class SessionSettings { //Load the container for the current widget[w] int containerToApply = loadWidgetSettings.getInt(loadedWidgetsArray[w]); - wm.widgets.get(widgetToActivate).setIsActive(true);//activate the new widget - wm.widgets.get(widgetToActivate).setContainer(containerToApply);//map it to the container that was loaded! + widgetManager.widgets.get(widgetToActivate).setIsActive(true);//activate the new widget + widgetManager.widgets.get(widgetToActivate).setContainer(containerToApply);//map it to the container that was loaded! println("LoadGUISettings: Applied Widget " + widgetToActivate + " to Container " + containerToApply); }//end case for all widget/container settings @@ -673,7 +673,7 @@ class SessionSettings { } else { hpWidgetNumber = 5; } - if (wm.widgets.get(hpWidgetNumber).getIsActive()) { + if (widgetManager.widgets.get(hpWidgetNumber).getIsActive()) { w_headPlot.headPlot.setPositionSize(w_headPlot.headPlot.hp_x, w_headPlot.headPlot.hp_y, w_headPlot.headPlot.hp_w, w_headPlot.headPlot.hp_h, w_headPlot.headPlot.hp_win_x, w_headPlot.headPlot.hp_win_y); println("Headplot is active: Redrawing"); } diff --git a/OpenBCI_GUI/TopNav.pde b/OpenBCI_GUI/TopNav.pde index 5a0d07df4..12c5050a7 100644 --- a/OpenBCI_GUI/TopNav.pde +++ b/OpenBCI_GUI/TopNav.pde @@ -698,16 +698,16 @@ class LayoutSelector { isVisible = !isVisible; if (isVisible) { //the very convoluted way of locking all controllers of a single controlP5 instance... - for (int i = 0; i < wm.widgets.size(); i++) { - for (int j = 0; j < wm.widgets.get(i).cp5_widget.getAll().size(); j++) { - wm.widgets.get(i).cp5_widget.getController(wm.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).lock(); + for (int i = 0; i < widgetManager.widgets.size(); i++) { + for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { + widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).lock(); } } } else { //the very convoluted way of unlocking all controllers of a single controlP5 instance... - for (int i = 0; i < wm.widgets.size(); i++) { - for (int j = 0; j < wm.widgets.get(i).cp5_widget.getAll().size(); j++) { - wm.widgets.get(i).cp5_widget.getController(wm.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).unlock(); + for (int i = 0; i < widgetManager.widgets.size(); i++) { + for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { + widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).unlock(); } } } @@ -728,7 +728,7 @@ class LayoutSelector { public void controlEvent(CallbackEvent theEvent) { output("Layout [" + (layoutNumber) + "] selected."); toggleVisibility(); //shut layoutSelector if something is selected - wm.setNewContainerLayout(layoutNumber); //have WidgetManager update Layout and active widgets + widgetManager.setNewContainerLayout(layoutNumber); //have WidgetManager update Layout and active widgets sessionSettings.currentLayout = layoutNumber; //copy this value to be used when saving Layout setting } }); @@ -883,17 +883,17 @@ class ConfigSelector { if (systemMode >= SYSTEMMODE_POSTINIT) { if (isVisible) { //the very convoluted way of locking all controllers of a single controlP5 instance... - for (int i = 0; i < wm.widgets.size(); i++) { - for (int j = 0; j < wm.widgets.get(i).cp5_widget.getAll().size(); j++) { - wm.widgets.get(i).cp5_widget.getController(wm.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).lock(); + for (int i = 0; i < widgetManager.widgets.size(); i++) { + for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { + widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).lock(); } } clearAllSettingsPressed = false; } else { //the very convoluted way of unlocking all controllers of a single controlP5 instance... - for (int i = 0; i < wm.widgets.size(); i++) { - for (int j = 0; j < wm.widgets.get(i).cp5_widget.getAll().size(); j++) { - wm.widgets.get(i).cp5_widget.getController(wm.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).unlock(); + for (int i = 0; i < widgetManager.widgets.size(); i++) { + for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { + widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).unlock(); } } } @@ -1191,16 +1191,16 @@ class TutorialSelector { if (systemMode >= SYSTEMMODE_POSTINIT) { if (isVisible) { //the very convoluted way of locking all controllers of a single controlP5 instance... - for (int i = 0; i < wm.widgets.size(); i++) { - for (int j = 0; j < wm.widgets.get(i).cp5_widget.getAll().size(); j++) { - wm.widgets.get(i).cp5_widget.getController(wm.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).lock(); + for (int i = 0; i < widgetManager.widgets.size(); i++) { + for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { + widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).lock(); } } } else { //the very convoluted way of unlocking all controllers of a single controlP5 instance... - for (int i = 0; i < wm.widgets.size(); i++) { - for (int j = 0; j < wm.widgets.get(i).cp5_widget.getAll().size(); j++) { - wm.widgets.get(i).cp5_widget.getController(wm.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).unlock(); + for (int i = 0; i < widgetManager.widgets.size(); i++) { + for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { + widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).unlock(); } } } diff --git a/OpenBCI_GUI/W_Accelerometer.pde b/OpenBCI_GUI/W_Accelerometer.pde index 8d7f7227f..b9038e4db 100644 --- a/OpenBCI_GUI/W_Accelerometer.pde +++ b/OpenBCI_GUI/W_Accelerometer.pde @@ -64,7 +64,7 @@ class W_Accelerometer extends Widget { accelerometerBar.adjustTimeAxis(horizontalScale.getValue()); accelerometerBar.adjustVertScale(verticalScale.getValue()); - createAccelModeButton("accelModeButton", "Turn Accel. Off", (int)(x + 1), (int)(y0 + navHeight + 1), 120, navHeight - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); + createAccelModeButton("accelModeButton", "Turn Accel. Off", (int)(x + 1), (int)(y0 + NAV_HEIGHT + 1), 120, NAV_HEIGHT - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); } float adjustYMaxMinBasedOnSource() { @@ -162,7 +162,7 @@ class W_Accelerometer extends Widget { //resize the accelerometer line graph accelerometerBar.screenResized(accelGraphX, accelGraphY, accelGraphWidth, accelGraphHeight); //bar x, bar y, bar w, bar h //update the position of the accel mode button - accelModeButton.setPosition((int)(x0 + 1), (int)(y0 + navHeight + 1)); + accelModeButton.setPosition((int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1)); } void mousePressed() { diff --git a/OpenBCI_GUI/W_AnalogRead.pde b/OpenBCI_GUI/W_AnalogRead.pde index 1f012c58e..804b9a354 100644 --- a/OpenBCI_GUI/W_AnalogRead.pde +++ b/OpenBCI_GUI/W_AnalogRead.pde @@ -56,7 +56,7 @@ class W_AnalogRead extends Widget { setVerticalScale(verticalScale.getIndex()); setHorizontalScale(horizontalScale.getIndex()); - createAnalogModeButton("analogModeButton", "Turn Analog Read On", (int)(x0 + 1), (int)(y0 + navHeight + 1), 128, navHeight - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); + createAnalogModeButton("analogModeButton", "Turn Analog Read On", (int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1), 128, NAV_HEIGHT - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); } public void update() { @@ -111,7 +111,7 @@ class W_AnalogRead extends Widget { analogReadBars[i].screenResized(int(ar_x), analogReadBarY, int(ar_w), analogReadBarHeight); //bar x, bar y, bar w, bar h } - analogModeButton.setPosition((int)(x0 + 1), (int)(y0 + navHeight + 1)); + analogModeButton.setPosition((int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1)); } public void mousePressed() { diff --git a/OpenBCI_GUI/W_BandPower.pde b/OpenBCI_GUI/W_BandPower.pde index 96e380bb8..0c185bdf2 100644 --- a/OpenBCI_GUI/W_BandPower.pde +++ b/OpenBCI_GUI/W_BandPower.pde @@ -65,8 +65,8 @@ class W_BandPower extends Widget { addDropdown("bandPowerDataFilteringDropdown", "Filtered?", filteredEnum.getEnumStringsAsList(), filteredEnum.getIndex()); // Setup for the BandPower plot - bp_plot = new GPlot(ourApplet, x, y-navHeight, w, h+navHeight); - // bp_plot.setPos(x, y+navHeight); + bp_plot = new GPlot(ourApplet, x, y-NAV_HEIGHT, w, h+NAV_HEIGHT); + // bp_plot.setPos(x, y+NAV_HEIGHT); bp_plot.setDim(w, h); bp_plot.setLogScale("y"); bp_plot.setYLim(0.1, 100); @@ -151,7 +151,7 @@ class W_BandPower extends Widget { //for this widget need to redraw the grey bar, bc the FFT plot covers it up... fill(200, 200, 200); - rect(x, y - navHeight, w, navHeight); //button bar + rect(x, y - NAV_HEIGHT, w, NAV_HEIGHT); //button bar popStyle(); bpChanSelect.draw(); diff --git a/OpenBCI_GUI/W_CytonImpedance.pde b/OpenBCI_GUI/W_CytonImpedance.pde index f5e8117fc..e6795f375 100644 --- a/OpenBCI_GUI/W_CytonImpedance.pde +++ b/OpenBCI_GUI/W_CytonImpedance.pde @@ -94,8 +94,8 @@ class W_CytonImpedance extends Widget { //Init the electrode map and fill and create signal check buttons initCytonImpedanceMap(); - cytonResetAllChannels = createCytonResetChannelsButton("cytonResetAllChannels", "Reset Channels", (int)(x0 + 1), (int)(y0 + navHeight + 1), 90, navHeight - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); - cytonImpedanceMasterCheck = createCytonImpMasterCheckButton("cytonImpedanceMasterCheck", "Check All Channels", (int)(x0 + 1 + padding_3 + 90), (int)(y0 + navHeight + 1), 120, navHeight - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); + cytonResetAllChannels = createCytonResetChannelsButton("cytonResetAllChannels", "Reset Channels", (int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1), 90, NAV_HEIGHT - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); + cytonImpedanceMasterCheck = createCytonImpMasterCheckButton("cytonImpedanceMasterCheck", "Check All Channels", (int)(x0 + 1 + padding_3 + 90), (int)(y0 + NAV_HEIGHT + 1), 120, NAV_HEIGHT - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); errorThreshold = new SignalCheckThresholdUI(threshold_ui_cp5, "errorThreshold", x + tableWidth + padding, y + h - navH, thresholdTFWidth, thresholdTFHeight, SIGNAL_CHECK_RED, signalCheckMode); warningThreshold = new SignalCheckThresholdUI(threshold_ui_cp5, "warningThreshold", x + tableWidth + padding, y + h - navH/2, thresholdTFWidth, thresholdTFHeight, SIGNAL_CHECK_YELLOW, signalCheckMode); } @@ -187,8 +187,8 @@ class W_CytonImpedance extends Widget { imp_buttons_cp5.setGraphics(ourApplet, 0, 0); threshold_ui_cp5.setGraphics(ourApplet, 0, 0); - cytonResetAllChannels.setPosition((int)(x0 + 1), (int)(y0 + navHeight + 1)); - cytonImpedanceMasterCheck.setPosition((int)(x0 + 1 + padding_3 + 90), (int)(y0 + navHeight + 1)); + cytonResetAllChannels.setPosition((int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1)); + cytonImpedanceMasterCheck.setPosition((int)(x0 + 1 + padding_3 + 90), (int)(y0 + NAV_HEIGHT + 1)); resizeTable(); diff --git a/OpenBCI_GUI/W_DigitalRead.pde b/OpenBCI_GUI/W_DigitalRead.pde index 7485b474c..d535cf69b 100644 --- a/OpenBCI_GUI/W_DigitalRead.pde +++ b/OpenBCI_GUI/W_DigitalRead.pde @@ -58,7 +58,7 @@ class W_DigitalRead extends Widget { digitalReadDots[i] = tempDot; } - createDigitalModeButton("digitalModeButton", "Turn Digital Read On", (int)(x0 + 1), (int)(y0 + navHeight + 1), 128, navHeight - 3, p5, 12, buttonsLightBlue, WHITE); + createDigitalModeButton("digitalModeButton", "Turn Digital Read On", (int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1), 128, NAV_HEIGHT - 3, p5, 12, buttonsLightBlue, WHITE); } public int getNumDigitalReads() { @@ -126,7 +126,7 @@ class W_DigitalRead extends Widget { } - digitalModeButton.setPosition((int)(x0 + 1), (int)(y0 + navHeight + 1)); + digitalModeButton.setPosition((int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1)); } public void mousePressed() { diff --git a/OpenBCI_GUI/W_FFT.pde b/OpenBCI_GUI/W_FFT.pde index 32f72e836..68ddfaca7 100644 --- a/OpenBCI_GUI/W_FFT.pde +++ b/OpenBCI_GUI/W_FFT.pde @@ -50,7 +50,7 @@ class W_fft extends Widget { void initializeFFTPlot() { //setup GPlot for FFT - fftPlot = new GPlot(ourApplet, x, y-navHeight, w, h+navHeight); + fftPlot = new GPlot(ourApplet, x, y-NAV_HEIGHT, w, h+NAV_HEIGHT); fftPlot.setAllFontProperties("Arial", 0, 14); fftPlot.getXAxis().setAxisLabelText("Frequency (Hz)"); fftPlot.getYAxis().setAxisLabelText("Amplitude (uV)"); @@ -142,7 +142,7 @@ class W_fft extends Widget { //for this widget need to redraw the grey bar, bc the FFT plot covers it up... fill(200, 200, 200); - rect(x, y - navHeight, w, navHeight); //button bar + rect(x, y - NAV_HEIGHT, w, NAV_HEIGHT); //button bar popStyle(); diff --git a/OpenBCI_GUI/W_Focus.pde b/OpenBCI_GUI/W_Focus.pde index eb2d164ba..f051581f7 100644 --- a/OpenBCI_GUI/W_Focus.pde +++ b/OpenBCI_GUI/W_Focus.pde @@ -184,7 +184,7 @@ class W_Focus extends Widget { } private void resizeTable() { - int extraPadding = focusChanSelect.isVisible() ? navHeight : 0; + int extraPadding = focusChanSelect.isVisible() ? NAV_HEIGHT : 0; float upperLeftContainerW = w/2; float upperLeftContainerH = h/2; //float min = min(upperLeftContainerW, upperLeftContainerH); @@ -199,9 +199,9 @@ class W_Focus extends Widget { } private void updateAuditoryNeurofeedbackPosition() { - int extraPadding = focusChanSelect.isVisible() ? navHeight : 0; + int extraPadding = focusChanSelect.isVisible() ? NAV_HEIGHT : 0; int subContainerMiddleX = x + w/4; - auditoryNeurofeedback.screenResized(subContainerMiddleX, (int)(y + h/2 - navHeight + extraPadding), w/2 - PAD_FIVE*2, navBarHeight/2); + auditoryNeurofeedback.screenResized(subContainerMiddleX, (int)(y + h/2 - NAV_HEIGHT + extraPadding), w/2 - PAD_FIVE*2, navBarHeight/2); } private void updateStatusCircle() { @@ -209,7 +209,7 @@ class W_Focus extends Widget { float upperLeftContainerH = h/2; float min = min(upperLeftContainerW, upperLeftContainerH); xc = x + w/4; - yc = y + h/4 - navHeight; + yc = y + h/4 - NAV_HEIGHT; wc = min * (3f/5); hc = wc; } @@ -354,7 +354,7 @@ class W_Focus extends Widget { void channelSelectFlexWidgetUI() { focusBar.setPlotPositionAndOuterDimensions(focusChanSelect.isVisible()); int factor = focusChanSelect.isVisible() ? 1 : -1; - yc += navHeight * factor; + yc += NAV_HEIGHT * factor; resizeTable(); updateAuditoryNeurofeedbackPosition(); } diff --git a/OpenBCI_GUI/W_GanglionImpedance.pde b/OpenBCI_GUI/W_GanglionImpedance.pde index f27ddc19c..4d0cd7600 100644 --- a/OpenBCI_GUI/W_GanglionImpedance.pde +++ b/OpenBCI_GUI/W_GanglionImpedance.pde @@ -18,7 +18,7 @@ class W_GanglionImpedance extends Widget { W_GanglionImpedance(String _widgetName) { super(_widgetName); - createStartStopCheck("startStopCheck", "Start Impedance Check", x + padding, y + padding, 200, navHeight, p4, 14, colorNotPressed, OPENBCI_DARKBLUE); + createStartStopCheck("startStopCheck", "Start Impedance Check", x + padding, y + padding, 200, NAV_HEIGHT, p4, 14, colorNotPressed, OPENBCI_DARKBLUE); } void update(){ diff --git a/OpenBCI_GUI/W_HeadPlot.pde b/OpenBCI_GUI/W_HeadPlot.pde index 2291493fa..f3b8f78e0 100644 --- a/OpenBCI_GUI/W_HeadPlot.pde +++ b/OpenBCI_GUI/W_HeadPlot.pde @@ -205,7 +205,7 @@ class HeadPlot { public void setPositionSize(int _x, int _y, int _w, int _h, int _win_x, int _win_y) { float percentMargin = 0.1; _x = _x + (int)(float(_w)*percentMargin); - _y = _y + (int)(float(_h)*percentMargin)-navHeight/2; + _y = _y + (int)(float(_h)*percentMargin)-NAV_HEIGHT/2; _w = (int)(float(_w)-(2*(float(_w)*percentMargin))); _h = (int)(float(_h)-(2*(float(_h)*percentMargin))); diff --git a/OpenBCI_GUI/W_Playback.pde b/OpenBCI_GUI/W_Playback.pde index 2245eaac5..a6e333dbb 100644 --- a/OpenBCI_GUI/W_Playback.pde +++ b/OpenBCI_GUI/W_Playback.pde @@ -30,7 +30,7 @@ class W_playback extends Widget { int initialWidth = w - padding*2; createPlaybackMenuList(cp5_playback, "playbackMenuList", x + padding/2, y + 2, initialWidth, h - padding*2, p3); - createSelectPlaybackFileButton("selectPlaybackFile_Session", "Select Playback File", x + w/2 - (padding*2), y - navHeight + 2, 200, navHeight - 6); + createSelectPlaybackFileButton("selectPlaybackFile_Session", "Select Playback File", x + w/2 - (padding*2), y - NAV_HEIGHT + 2, 200, NAV_HEIGHT - 6); } void update() { @@ -84,7 +84,7 @@ class W_playback extends Widget { cp5_playback.setGraphics(ourApplet, 0, 0); //Resize and position cp5 objects within this widget - selectPlaybackFileButton.setPosition(x + w - selectPlaybackFileButton.getWidth() - 2, y - navHeight + 2); + selectPlaybackFileButton.setPosition(x + w - selectPlaybackFileButton.getWidth() - 2, y - NAV_HEIGHT + 2); playbackMenuList.setPosition(x + padding/2, y + 2); playbackMenuList.setSize(w - padding*2, h - padding*2); diff --git a/OpenBCI_GUI/W_PulseSensor.pde b/OpenBCI_GUI/W_PulseSensor.pde index db0045247..502b131b1 100644 --- a/OpenBCI_GUI/W_PulseSensor.pde +++ b/OpenBCI_GUI/W_PulseSensor.pde @@ -76,7 +76,7 @@ class W_PulseSensor extends Widget { setPulseWidgetVariables(); initializePulseFinderVariables(); - createAnalogModeButton("pulseSensorAnalogModeButton", "Turn Analog Read On", (int)(x0 + 1), (int)(y0 + navHeight + 1), 128, navHeight - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); + createAnalogModeButton("pulseSensorAnalogModeButton", "Turn Analog Read On", (int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1), 128, NAV_HEIGHT - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); } public void update(){ @@ -131,7 +131,7 @@ class W_PulseSensor extends Widget { super.screenResized(); setPulseWidgetVariables(); - analogModeButton.setPosition((int)(x0 + 1), (int)(y0 + navHeight + 1)); + analogModeButton.setPosition((int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1)); } private void createAnalogModeButton(String name, String text, int _x, int _y, int _w, int _h, PFont _font, int _fontSize, color _bg, color _textColor) { diff --git a/OpenBCI_GUI/W_Template.pde b/OpenBCI_GUI/W_Template.pde index ce20a62bf..c8c299fae 100644 --- a/OpenBCI_GUI/W_Template.pde +++ b/OpenBCI_GUI/W_Template.pde @@ -80,7 +80,7 @@ class W_template extends Widget { //You can find more detailed examples in the Control Panel, where there are many UI objects with varying functionality. private void createWidgetTemplateButton() { //This is a generalized createButton method that allows us to save code by using a few patterns and method overloading - widgetTemplateButton = createButton(localCP5, "widgetTemplateButton", "Design Your Own Widget!", x + w/2, y + h/2, 200, navHeight, p4, 14, colorNotPressed, OPENBCI_DARKBLUE); + widgetTemplateButton = createButton(localCP5, "widgetTemplateButton", "Design Your Own Widget!", x + w/2, y + h/2, 200, NAV_HEIGHT, p4, 14, colorNotPressed, OPENBCI_DARKBLUE); //Set the border color explicitely widgetTemplateButton.setBorderColor(OBJECT_BORDER_GREY); //For this button, only call the callback listener on mouse release diff --git a/OpenBCI_GUI/W_TimeSeries.pde b/OpenBCI_GUI/W_TimeSeries.pde index fc22a6132..fd9baf96d 100644 --- a/OpenBCI_GUI/W_TimeSeries.pde +++ b/OpenBCI_GUI/W_TimeSeries.pde @@ -123,7 +123,7 @@ class W_timeSeries extends Widget { int h_hsc = channelBarHeight * numChannelBars; if (currentBoard instanceof ADS1299SettingsBoard) { - hwSettingsButton = createHSCButton("HardwareSettings", "Hardware Settings", (int)(x0 + 80), (int)(y0 + navHeight + 1), 120, navHeight - 3); + hwSettingsButton = createHSCButton("HardwareSettings", "Hardware Settings", (int)(x0 + 80), (int)(y0 + NAV_HEIGHT + 1), 120, NAV_HEIGHT - 3); cp5ElementsToCheck.add((controlP5.Controller)hwSettingsButton); adsSettingsController = new ADS1299SettingsController(ourApplet, tsChanSelect.getActiveChannels(), x_hsc, y_hsc, w_hsc, h_hsc, channelBarHeight); } @@ -256,7 +256,7 @@ class W_timeSeries extends Widget { } if (currentBoard instanceof ADS1299SettingsBoard) { - hwSettingsButton.setPosition(x0 + 80, (int)(y0 + navHeight + 1)); + hwSettingsButton.setPosition(x0 + 80, (int)(y0 + NAV_HEIGHT + 1)); } } diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index 0b69181c6..85ca593c6 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -225,8 +225,8 @@ class Widget { private void resizeWidgetSelector() { int dropdownsItemsToShow = int((h0 * widgetDropdownScaling) / (navH - 4)); widgetSelectorHeight = (dropdownsItemsToShow + 1) * (navH - 4); - if (wm != null) { - int maxDropdownHeight = (wm.widgetOptions.size() + 1) * (navH - 4); + if (widgetManager != null) { + int maxDropdownHeight = (widgetManager.widgetOptions.size() + 1) * (navH - 4); if (widgetSelectorHeight > maxDropdownHeight) widgetSelectorHeight = maxDropdownHeight; } @@ -260,7 +260,7 @@ class Widget { for(int i = 0; i < dropdowns.size(); i++){ int dropdownPos = dropdowns.size() - i; cp5_widget.getController(dropdowns.get(i).id) - //.setPosition(w-(dropdownWidth*dropdownPos)-(2*(dropdownPos+1)), navHeight+(y+2)) // float left + //.setPosition(w-(dropdownWidth*dropdownPos)-(2*(dropdownPos+1)), NAV_HEIGHT+(y+2)) // float left .setPosition(x0+w0-(dropdownWidth*(dropdownPos))-(2*(dropdownPos)), navH +(y0+2)) //float right //.setSize(dropdownWidth, (maxFreqList.size()+1)*(navBarHeight-4)) ; @@ -359,23 +359,23 @@ class NavBarDropdown{ void WidgetSelector(int n){ println("New widget [" + n + "] selected for container..."); //find out if the widget you selected is already active - boolean isSelectedWidgetActive = wm.widgets.get(n).getIsActive(); + boolean isSelectedWidgetActive = widgetManager.widgets.get(n).getIsActive(); //find out which widget & container you are currently in... int theContainer = -1; - for(int i = 0; i < wm.widgets.size(); i++){ - if(wm.widgets.get(i).isMouseHere()){ - theContainer = wm.widgets.get(i).currentContainer; //keep track of current container (where mouse is...) + for(int i = 0; i < widgetManager.widgets.size(); i++){ + if(widgetManager.widgets.get(i).isMouseHere()){ + theContainer = widgetManager.widgets.get(i).currentContainer; //keep track of current container (where mouse is...) if(isSelectedWidgetActive){ //if the selected widget was already active - wm.widgets.get(i).setContainer(wm.widgets.get(n).currentContainer); //just switch the widget locations (ie swap containers) + widgetManager.widgets.get(i).setContainer(widgetManager.widgets.get(n).currentContainer); //just switch the widget locations (ie swap containers) } else{ - wm.widgets.get(i).setIsActive(false); //deactivate the current widget (if it is different than the one selected) + widgetManager.widgets.get(i).setIsActive(false); //deactivate the current widget (if it is different than the one selected) } } } - wm.widgets.get(n).setIsActive(true);//activate the new widget - wm.widgets.get(n).setContainer(theContainer);//map it to the current container + widgetManager.widgets.get(n).setIsActive(true);//activate the new widget + widgetManager.widgets.get(n).setContainer(theContainer);//map it to the current container } diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index 7a49784db..6cd8d4b6f 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -37,43 +37,32 @@ class WidgetManager{ //this holds all of the widgets ... when creating/adding new widgets, we will add them to this ArrayList (below) ArrayList widgets; ArrayList widgetOptions; //List of Widget Titles, used to populate cp5 widgetSelector dropdown of all widgets - - //Variables for int currentContainerLayout; //this is the Layout structure for the main body of the GUI ... refer to [PUT_LINK_HERE] for layouts/numbers image ArrayList layouts = new ArrayList(); //this holds all of the different layouts ... - public boolean isWMInitialized = false; private boolean visible = true; WidgetManager() { widgets = new ArrayList(); widgetOptions = new ArrayList(); - isWMInitialized = false; //DO NOT re-order the functions below setupLayouts(); setupWidgets(); setupWidgetSelectorDropdowns(); - if(globalChannelCount == 4 && eegDataSource == DATASOURCE_GANGLION) { - currentContainerLayout = 1; - sessionSettings.currentLayout = 1; // used for save/load settings - setNewContainerLayout(currentContainerLayout); //sets and fills layout with widgets in order of widget index, to reorganize widget index, reorder the creation in setupWidgets() - } else if (eegDataSource == DATASOURCE_PLAYBACKFILE) { + if((globalChannelCount == 4 && eegDataSource == DATASOURCE_GANGLION) || eegDataSource == DATASOURCE_PLAYBACKFILE) { currentContainerLayout = 1; - sessionSettings.currentLayout = 1; // used for save/load settings - setNewContainerLayout(currentContainerLayout); //sets and fills layout with widgets in order of widget index, to reorganize widget index, reorder the creation in setupWidgets() + sessionSettings.currentLayout = 1; } else { currentContainerLayout = 4; //default layout ... tall container left and 2 shorter containers stacked on the right - sessionSettings.currentLayout = 4; // used for save/load settings - setNewContainerLayout(currentContainerLayout); //sets and fills layout with widgets in order of widget index, to reorganize widget index, reorder the creation in setupWidgets() + sessionSettings.currentLayout = 4; } - isWMInitialized = true; + setNewContainerLayout(currentContainerLayout); //sets and fills layout with widgets in order of widget index, to reorganize widget index, reorder the creation in setupWidgets() } void setupWidgets() { - // println(" setupWidgets start -- " + millis()); w_timeSeries = new W_timeSeries("Time Series"); widgets.add(w_timeSeries); From bdd3dd9814c8120b04e5895453ec7b526fc14bd3 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 10 Apr 2025 17:11:18 -0500 Subject: [PATCH 08/29] Refactor locking/unlocking Widget Settings dropdowns when TopNav UIs are selected --- OpenBCI_GUI/TopNav.pde | 54 +++++------------------------------ OpenBCI_GUI/WidgetManager.pde | 11 +++++++ 2 files changed, 18 insertions(+), 47 deletions(-) diff --git a/OpenBCI_GUI/TopNav.pde b/OpenBCI_GUI/TopNav.pde index 12c5050a7..1c839dc27 100644 --- a/OpenBCI_GUI/TopNav.pde +++ b/OpenBCI_GUI/TopNav.pde @@ -696,20 +696,8 @@ class LayoutSelector { void toggleVisibility() { isVisible = !isVisible; - if (isVisible) { - //the very convoluted way of locking all controllers of a single controlP5 instance... - for (int i = 0; i < widgetManager.widgets.size(); i++) { - for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { - widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).lock(); - } - } - } else { - //the very convoluted way of unlocking all controllers of a single controlP5 instance... - for (int i = 0; i < widgetManager.widgets.size(); i++) { - for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { - widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).unlock(); - } - } + if (widgetManager != null) { + widgetManager.lockCp5ObjectsInAllWidgets(isVisible); } } @@ -880,23 +868,9 @@ class ConfigSelector { void toggleVisibility() { isVisible = !isVisible; - if (systemMode >= SYSTEMMODE_POSTINIT) { - if (isVisible) { - //the very convoluted way of locking all controllers of a single controlP5 instance... - for (int i = 0; i < widgetManager.widgets.size(); i++) { - for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { - widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).lock(); - } - } - clearAllSettingsPressed = false; - } else { - //the very convoluted way of unlocking all controllers of a single controlP5 instance... - for (int i = 0; i < widgetManager.widgets.size(); i++) { - for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { - widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).unlock(); - } - } - } + if (widgetManager != null) { + widgetManager.lockCp5ObjectsInAllWidgets(isVisible); + clearAllSettingsPressed = !isVisible; } //When closed by any means and confirmation buttons are open... @@ -1188,22 +1162,8 @@ class TutorialSelector { void toggleVisibility() { isVisible = !isVisible; - if (systemMode >= SYSTEMMODE_POSTINIT) { - if (isVisible) { - //the very convoluted way of locking all controllers of a single controlP5 instance... - for (int i = 0; i < widgetManager.widgets.size(); i++) { - for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { - widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).lock(); - } - } - } else { - //the very convoluted way of unlocking all controllers of a single controlP5 instance... - for (int i = 0; i < widgetManager.widgets.size(); i++) { - for (int j = 0; j < widgetManager.widgets.get(i).cp5_widget.getAll().size(); j++) { - widgetManager.widgets.get(i).cp5_widget.getController(widgetManager.widgets.get(i).cp5_widget.getAll().get(j).getAddress()).unlock(); - } - } - } + if (widgetManager != null) { + widgetManager.lockCp5ObjectsInAllWidgets(isVisible); } } diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index 6cd8d4b6f..32585c636 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -347,6 +347,17 @@ class WidgetManager{ println("Widget Manager: All widgets set to null."); } + + // Useful in places like TopNav which overlap widget dropdowns + public void lockCp5ObjectsInAllWidgets(boolean lock) { + for (int i = 0; i < widgets.size(); i++) { + for (int j = 0; j < widgets.get(i).cp5_widget.getAll().size(); j++) { + ControlP5 cp5Instance = widgets.get(i).cp5_widget; + String widgetAddress = cp5Instance.getAll().get(j).getAddress(); + cp5Instance.getController(widgetAddress).setLock(lock); + } + } + } }; //the Layout class is an orgnanizational tool ... a layout consists of a combination of containers ... refer to Container.pde From b4c5fd110137d3064386babd8e5645b4bca08689 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 10 Apr 2025 20:52:50 -0500 Subject: [PATCH 09/29] Refactor WidgetManager to contain all Widgets --- OpenBCI_GUI/ADS1299SettingsController.pde | 5 +- OpenBCI_GUI/ControlPanel.pde | 6 +- OpenBCI_GUI/CytonElectrodeStatus.pde | 3 +- OpenBCI_GUI/DataProcessing.pde | 14 +- OpenBCI_GUI/EmgJoystickEnums.pde | 111 +++++++++++ OpenBCI_GUI/Extras.pde | 5 + OpenBCI_GUI/Interactivity.pde | 3 +- OpenBCI_GUI/Layout.pde | 26 +++ OpenBCI_GUI/NetworkingDataAccumulator.pde | 10 +- OpenBCI_GUI/OpenBCI_GUI.pde | 9 +- OpenBCI_GUI/PopupMessageHardwareSettings.pde | 2 +- OpenBCI_GUI/SessionSettings.pde | 55 ++++-- OpenBCI_GUI/SignalCheckThresholds.pde | 5 +- OpenBCI_GUI/TopNav.pde | 6 +- OpenBCI_GUI/W_Accelerometer.pde | 14 +- OpenBCI_GUI/W_AnalogRead.pde | 16 +- OpenBCI_GUI/W_BandPower.pde | 10 +- OpenBCI_GUI/W_CytonImpedance.pde | 29 +-- OpenBCI_GUI/W_DigitalRead.pde | 12 +- OpenBCI_GUI/W_EMG.pde | 4 +- OpenBCI_GUI/W_EMGJoystick.pde | 117 +---------- OpenBCI_GUI/W_FFT.pde | 14 +- OpenBCI_GUI/W_Focus.pde | 6 +- OpenBCI_GUI/W_GanglionImpedance.pde | 13 -- OpenBCI_GUI/W_HeadPlot.pde | 87 ++++----- OpenBCI_GUI/W_Marker.pde | 14 +- OpenBCI_GUI/W_PulseSensor.pde | 12 +- OpenBCI_GUI/W_Spectrogram.pde | 8 +- OpenBCI_GUI/W_Template.pde | 6 +- OpenBCI_GUI/W_TimeSeries.pde | 21 +- OpenBCI_GUI/Widget.pde | 1 + OpenBCI_GUI/WidgetManager.pde | 194 ++++++------------- OpenBCI_GUI/WidgetSettings.pde | 166 ++++++++++++++++ 33 files changed, 573 insertions(+), 431 deletions(-) create mode 100644 OpenBCI_GUI/EmgJoystickEnums.pde create mode 100644 OpenBCI_GUI/Layout.pde create mode 100644 OpenBCI_GUI/WidgetSettings.pde diff --git a/OpenBCI_GUI/ADS1299SettingsController.pde b/OpenBCI_GUI/ADS1299SettingsController.pde index 972a4065c..0a9de3ef8 100644 --- a/OpenBCI_GUI/ADS1299SettingsController.pde +++ b/OpenBCI_GUI/ADS1299SettingsController.pde @@ -750,8 +750,9 @@ void loadHardwareSettings(File selection) { if (((ADS1299SettingsBoard)currentBoard).getADS1299Settings().loadSettingsValues(selection.getAbsolutePath())) { outputSuccess("Hardware Settings Loaded!"); for (int i = 0; i < globalChannelCount; i++) { - w_timeSeries.adsSettingsController.updateChanSettingsDropdowns(i, currentBoard.isEXGChannelActive(i)); - w_timeSeries.adsSettingsController.updateHasUnappliedSettings(i); + W_TimeSeries timeSeriesWidget = widgetManager.getTimeSeriesWidget(); + timeSeriesWidget.adsSettingsController.updateChanSettingsDropdowns(i, currentBoard.isEXGChannelActive(i)); + timeSeriesWidget.adsSettingsController.updateHasUnappliedSettings(i); } } else { outputError("Failed to load Hardware Settings."); diff --git a/OpenBCI_GUI/ControlPanel.pde b/OpenBCI_GUI/ControlPanel.pde index 111e4acf5..687f42861 100644 --- a/OpenBCI_GUI/ControlPanel.pde +++ b/OpenBCI_GUI/ControlPanel.pde @@ -2506,8 +2506,10 @@ class InitBox { //creates new data file name so that you don't accidentally overwrite the old one controlPanel.dataLogBoxCyton.setSessionTextfieldText(directoryManager.getFileNameDateTime()); controlPanel.dataLogBoxGanglion.setSessionTextfieldText(directoryManager.getFileNameDateTime()); - w_focus.killAuditoryFeedback(); - w_marker.disposeUdpMarkerReceiver(); + W_Focus focusWidget = (W_Focus) widgetManager.getWidget("W_Focus"); + W_Marker markerWidget = (W_Marker) widgetManager.getWidget("W_Marker"); + focusWidget.killAuditoryFeedback(); + markerWidget.disposeUdpMarkerReceiver(); haltSystem(); widgetManager.setAllWidgetsNull(); } diff --git a/OpenBCI_GUI/CytonElectrodeStatus.pde b/OpenBCI_GUI/CytonElectrodeStatus.pde index 5772b5174..e3b5588af 100644 --- a/OpenBCI_GUI/CytonElectrodeStatus.pde +++ b/OpenBCI_GUI/CytonElectrodeStatus.pde @@ -346,7 +346,8 @@ class CytonElectrodeStatus { final int _chan = channelNumber - 1; final int curMillis = millis(); println("CytonElectrodeTestButton: Toggling Impedance on ~~ " + electrodeLocation); - w_cytonImpedance.toggleImpedanceOnElectrode(!cytonBoard.isCheckingImpedanceNorP(_chan, is_N_Pin), _chan, is_N_Pin, curMillis); + W_CytonImpedance cytonImpedanceWidget = (W_CytonImpedance) widgetManager.getWidget("W_CytonImpedance"); + cytonImpedanceWidget.toggleImpedanceOnElectrode(!cytonBoard.isCheckingImpedanceNorP(_chan, is_N_Pin), _chan, is_N_Pin, curMillis); } }); testing_button.setDescription("Click to toggle impedance check for this ADS pin."); diff --git a/OpenBCI_GUI/DataProcessing.pde b/OpenBCI_GUI/DataProcessing.pde index 2d113528e..c5de43c14 100644 --- a/OpenBCI_GUI/DataProcessing.pde +++ b/OpenBCI_GUI/DataProcessing.pde @@ -321,12 +321,12 @@ class DataProcessing { // -RW #1094 // ///////////////////////////////////////////////////////////// emgSettings.values.process(dataProcessingFilteredBuffer); - w_focus.updateFocusWidgetData(); - w_bandPower.updateBandPowerWidgetData(); - w_emgJoystick.updateEmgJoystickWidgetData(); + ((W_Focus) widgetManager.getWidget("W_Focus")).updateFocusWidgetData(); + ((W_BandPower) widgetManager.getWidget("W_BandPower")).updateBandPowerWidgetData(); + ((W_EmgJoystick) widgetManager.getWidget("W_EmgJoystick")).updateEmgJoystickWidgetData(); if (currentBoard instanceof BoardCyton) { - if (w_pulseSensor != null) { - w_pulseSensor.updatePulseSensorWidgetData(); + if (widgetManager.getWidgetExists("W_PulseSensor")) { + ((W_PulseSensor) widgetManager.getWidget("W_PulseSensor")).updatePulseSensorWidgetData(); } } @@ -381,7 +381,7 @@ class DataProcessing { private void clearCalculatedMetricWidgets() { println("Clearing calculated metric widgets"); - w_spectrogram.clear(); - w_focus.clear(); + ((W_Spectrogram) widgetManager.getWidget("W_Spectrogram")).clear(); + ((W_Focus) widgetManager.getWidget("W_Focus")).clear(); } } \ No newline at end of file diff --git a/OpenBCI_GUI/EmgJoystickEnums.pde b/OpenBCI_GUI/EmgJoystickEnums.pde new file mode 100644 index 000000000..409d2b197 --- /dev/null +++ b/OpenBCI_GUI/EmgJoystickEnums.pde @@ -0,0 +1,111 @@ + +public enum EmgJoystickSmoothing implements IndexingInterface +{ + OFF (0, "Off", 0f), + POINT_9 (1, "0.9", .9f), + POINT_95 (2, "0.95", .95f), + POINT_98 (3, "0.98", .98f), + POINT_99 (4, "0.99", .99f), + POINT_999 (5, "0.999", .999f), + POINT_9999 (6, "0.9999", .9999f); + + private int index; + private String name; + private float value; + private static EmgJoystickSmoothing[] vals = values(); + + EmgJoystickSmoothing(int index, String name, float value) { + this.index = index; + this.name = name; + this.value = value; + } + + public int getIndex() { + return index; + } + + public String getString() { + return name; + } + + public float getValue() { + return value; + } + + private static List getEnumStringsAsList() { + List enumStrings = new ArrayList(); + for (IndexingInterface val : vals) { + enumStrings.add(val.getString()); + } + return enumStrings; + } +} + +public class EMGJoystickInput { + private int index; + private String name; + private int value; + + EMGJoystickInput(int index, String name, int value) { + this.index = index; + this.name = name; + this.value = value; + } + + public int getIndex() { + return index; + } + + public String getString() { + return name; + } + + public int getValue() { + return value; + } +} + +public class EMGJoystickInputs { + private final int NUM_EMG_INPUTS = 4; + private final EMGJoystickInput[] VALUES; + private final EMGJoystickInput[] INPUTS = new EMGJoystickInput[NUM_EMG_INPUTS]; + + EMGJoystickInputs(int numExGChannels) { + VALUES = new EMGJoystickInput[numExGChannels]; + for (int i = 0; i < numExGChannels; i++) { + VALUES[i] = new EMGJoystickInput(i, "Channel " + (i + 1), i); + } + } + + public EMGJoystickInput[] getValues() { + return VALUES; + } + + public EMGJoystickInput[] getInputs() { + return INPUTS; + } + + public EMGJoystickInput getInput(int index) { + return INPUTS[index]; + } + + public void setInputToChannel(int inputNumber, int channel) { + if (inputNumber < 0 || inputNumber >= NUM_EMG_INPUTS) { + println("Invalid input number: " + inputNumber); + return; + } + if (channel < 0 || channel >= VALUES.length) { + println("Invalid channel: " + channel); + return; + } + INPUTS[inputNumber] = VALUES[channel]; + } + + public List getValueStringsAsList() { + List enumStrings = new ArrayList(); + for (EMGJoystickInput val : VALUES) { + enumStrings.add(val.getString()); + } + return enumStrings; + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/Extras.pde b/OpenBCI_GUI/Extras.pde index f37587d10..c6d664cc7 100644 --- a/OpenBCI_GUI/Extras.pde +++ b/OpenBCI_GUI/Extras.pde @@ -416,6 +416,11 @@ void doubleToFloatArray(double[] array, float[] res) { } } +public double convertByteArrayToDouble(byte[] array) { + ByteBuffer buffer = ByteBuffer.wrap(array); + return buffer.getDouble(); +} + // shortens a string to a given width by adding [...] in the middle // make sure to pass the right font for accurate sizing String shortenString(String str, float maxWidth, PFont font) { diff --git a/OpenBCI_GUI/Interactivity.pde b/OpenBCI_GUI/Interactivity.pde index 10d579687..3307b4b99 100644 --- a/OpenBCI_GUI/Interactivity.pde +++ b/OpenBCI_GUI/Interactivity.pde @@ -225,7 +225,8 @@ void parseKey(char val) { } // Check for software marker keyboard shortcuts - if (w_marker.checkForMarkerKeyPress(val)) { + W_Marker markerWidget = (W_Marker) widgetManager.getWidget("W_Marker"); + if (markerWidget.checkForMarkerKeyPress(val)) { return; } diff --git a/OpenBCI_GUI/Layout.pde b/OpenBCI_GUI/Layout.pde new file mode 100644 index 000000000..8dcbc732f --- /dev/null +++ b/OpenBCI_GUI/Layout.pde @@ -0,0 +1,26 @@ + +//The Layout class is an organizational tool. A layout consists of a combination of containers (found in Container.pde). +class Layout { + + Container[] myContainers; + int[] containerInts; + + Layout(int[] _myContainers){ //when creating a new layout, you pass in the integer #s of the containers you want as part of the layout ... so if I pass in the array {5}, my layout is 1 container that takes up the whole GUI body + //constructor stuff + myContainers = new Container[_myContainers.length]; //make the myContainers array equal to the size of the incoming array of ints + containerInts = new int[_myContainers.length]; + for(int i = 0; i < _myContainers.length; i++){ + myContainers[i] = container[_myContainers[i]]; + containerInts[i] = _myContainers[i]; + } + } + + Container getContainer(int _numContainer){ + if(_numContainer < myContainers.length){ + return myContainers[_numContainer]; + } else{ + println("Widget Manager: Tried to return a non-existant container..."); + return myContainers[myContainers.length-1]; + } + } +}; \ No newline at end of file diff --git a/OpenBCI_GUI/NetworkingDataAccumulator.pde b/OpenBCI_GUI/NetworkingDataAccumulator.pde index 5c9d2074e..ec4477a9d 100644 --- a/OpenBCI_GUI/NetworkingDataAccumulator.pde +++ b/OpenBCI_GUI/NetworkingDataAccumulator.pde @@ -294,7 +294,7 @@ public class NetworkingDataAccumulator { } public float[] getNormalizedBandPowerData() { - return w_bandPower.getNormalizedBPSelectedChannels(); + return ((W_BandPower) widgetManager.getWidget("W_BandPower")).getNormalizedBPSelectedChannels(); } public float[] getEmgNormalizedValues() { @@ -302,18 +302,18 @@ public class NetworkingDataAccumulator { } public int getPulseSensorBPM() { - return w_pulseSensor.getBPM(); + return ((W_PulseSensor) widgetManager.getWidget("W_PulseSensor")).getBPM(); } public int getPulseSensorIBI() { - return w_pulseSensor.getIBI(); + return ((W_PulseSensor) widgetManager.getWidget("W_PulseSensor")).getIBI(); } public int getFocusValueExceedsThreshold() { - return w_focus.getMetricExceedsThreshold(); + return ((W_Focus) widgetManager.getWidget("W_Focus")).getMetricExceedsThreshold(); } public float[] getEMGJoystickXY() { - return w_emgJoystick.getJoystickXY(); + return ((W_EmgJoystick) widgetManager.getWidget("W_EmgJoystick")).getJoystickXY(); } } \ No newline at end of file diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde index 3793642cd..f2145c249 100644 --- a/OpenBCI_GUI/OpenBCI_GUI.pde +++ b/OpenBCI_GUI/OpenBCI_GUI.pde @@ -861,8 +861,9 @@ void startRunning() { output("Data stream started."); // todo: this should really be some sort of signal that listeners can register for "OnStreamStarted" // close hardware settings if user starts streaming - if (w_timeSeries.getAdsSettingsVisible()) { - w_timeSeries.closeADSSettings(); + W_TimeSeries timeSeriesWidget = widgetManager.getTimeSeriesWidget(); + if (timeSeriesWidget.getAdsSettingsVisible()) { + timeSeriesWidget.closeADSSettings(); } try { streamTimeElapsed.reset(); @@ -918,8 +919,8 @@ void haltSystem() { } } - if (w_focus != null) { - w_focus.endSession(); + if (widgetManager.getWidgetExists("W_Focus")) { + ((W_Focus) widgetManager.getWidget("W_Focus")).endSession(); } stopRunning(); diff --git a/OpenBCI_GUI/PopupMessageHardwareSettings.pde b/OpenBCI_GUI/PopupMessageHardwareSettings.pde index f3ba3473b..bb2100d5c 100644 --- a/OpenBCI_GUI/PopupMessageHardwareSettings.pde +++ b/OpenBCI_GUI/PopupMessageHardwareSettings.pde @@ -92,7 +92,7 @@ class PopupMessageHardwareSettings extends PopupMessage { myButton.onPress(new CallbackListener() { public void controlEvent(CallbackEvent theEvent) { topNav.dataStreamTogglePressed(); - w_timeSeries.setAdsSettingsVisible(true); + widgetManager.getTimeSeriesWidget().setAdsSettingsVisible(true); Frame frame = ( (PSurfaceAWT.SmoothCanvas) ((PSurfaceAWT)surface).getNative()).getFrame(); frame.dispose(); exit(); diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde index 9ac6e74f5..95428a1a8 100644 --- a/OpenBCI_GUI/SessionSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -262,6 +262,8 @@ class SessionSettings { //Make a new JSON Object for Time Series Settings JSONObject saveTSSettings = new JSONObject(); + //FIX ME + /* saveTSSettings.setInt("Time Series Vert Scale", w_timeSeries.getVerticalScale().getIndex()); saveTSSettings.setInt("Time Series Horiz Scale", w_timeSeries.getHorizontalScale().getIndex()); saveTSSettings.setInt("Time Series Label Mode", w_timeSeries.getLabelMode().getIndex()); @@ -273,6 +275,7 @@ class SessionSettings { saveActiveChanTS.setInt(i, activeChannel); } saveTSSettings.setJSONArray("activeChannels", saveActiveChanTS); + */ saveSettingsJSONData.setJSONObject(kJSONKeyTimeSeries, saveTSSettings); //Make a second JSON object within our JSONArray to store Global settings for the GUI @@ -332,8 +335,8 @@ class SessionSettings { saveSettingsJSONData.setJSONObject(kJSONKeyNetworking, saveNetworkingSettings); ///////////////////////////////////////////////Setup new JSON object to save Headplot settings - if (w_headPlot != null) { - JSONObject saveHeadplotSettings = new JSONObject(); + //if (w_headPlot != null) { + JSONObject saveHeadplotSettings = new JSONObject(); //FIX ME /* @@ -347,8 +350,8 @@ class SessionSettings { saveHeadplotSettings.setInt("HP_smoothing", hpSmoothingSave); //Set the Headplot JSON Object */ - saveSettingsJSONData.setJSONObject(kJSONKeyHeadplot, saveHeadplotSettings); - } + saveSettingsJSONData.setJSONObject(kJSONKeyHeadplot, saveHeadplotSettings); + //} ///////////////////////////////////////////////Setup new JSON object to save Band Power settings JSONObject saveBPSettings = new JSONObject(); @@ -368,11 +371,13 @@ class SessionSettings { saveBPSettings.setInt("bpAutoCleanTimer", w_bandPower.getAutoCleanTimer().getIndex()); */ saveSettingsJSONData.setJSONObject(kJSONKeyBandPower, saveBPSettings); - + ///////////////////////////////////////////////Setup new JSON object to save Spectrogram settings JSONObject saveSpectrogramSettings = new JSONObject(); //Save data from the Active channel checkBoxes - Top - JSONArray saveActiveChanSpectTop = new JSONArray(); + //JSONArray saveActiveChanSpectTop = new JSONArray(); + /* + //FIX ME int numActiveSpectChanTop = w_spectrogram.spectChanSelectTop.getActiveChannels().size(); for (int i = 0; i < numActiveSpectChanTop; i++) { int activeChannel = w_spectrogram.spectChanSelectTop.getActiveChannels().get(i); @@ -386,7 +391,8 @@ class SessionSettings { int activeChannel = w_spectrogram.spectChanSelectBot.getActiveChannels().get(i); saveActiveChanSpectBot.setInt(i, activeChannel); } - saveSpectrogramSettings.setJSONArray("activeChannelsBot", saveActiveChanSpectBot); + */ + //saveSpectrogramSettings.setJSONArray("activeChannelsBot", saveActiveChanSpectBot); //Save Spectrogram_Max Freq Setting. The max frq variable is updated every time the user selects a dropdown in the spectrogram widget //FIX ME /* @@ -401,14 +407,19 @@ class SessionSettings { //Save data from the Active channel checkBoxes JSONArray saveActiveChanEMG = new JSONArray(); + //FIX ME + /* int numActiveEMGChan = w_emg.emgChannelSelect.getActiveChannels().size(); for (int i = 0; i < numActiveEMGChan; i++) { int activeChannel = w_emg.emgChannelSelect.getActiveChannels().get(i); saveActiveChanEMG.setInt(i, activeChannel); } + */ saveEMGSettings.setJSONArray("activeChannels", saveActiveChanEMG); saveSettingsJSONData.setJSONObject(kJSONKeyEmg, saveEMGSettings); + /* + //FIX ME ///////////////////////////////////////////////Setup new JSON object to save EMG Joystick Settings JSONObject saveEmgJoystickSettings = new JSONObject(); saveEmgJoystickSettings.setInt("smoothing", w_emgJoystick.joystickSmoothing.getIndex()); @@ -431,6 +442,7 @@ class SessionSettings { saveFocusSettings.setInt("focusThreshold", w_focus.getFocusThreshold().getIndex()); saveFocusSettings.setInt("focusWindow", w_focus.getFocusWindow().getIndex()); saveSettingsJSONData.setJSONObject(kJSONKeyFocus, saveFocusSettings); + */ ///////////////////////////////////////////////Setup new JSON object to save Widgets Active in respective Containers JSONObject saveWidgetSettings = new JSONObject(); @@ -535,7 +547,7 @@ class SessionSettings { loadNetworkingSettings = loadSettingsJSONData.getJSONObject(kJSONKeyNetworking); //get the Headplot settings - if (w_headPlot != null) { + //if (w_headPlot != null) { //FIX ME /* JSONObject loadHeadplotSettings = loadSettingsJSONData.getJSONObject(kJSONKeyHeadplot); @@ -544,7 +556,7 @@ class SessionSettings { hpContoursLoad = loadHeadplotSettings.getInt("HP_contours"); hpSmoothingLoad = loadHeadplotSettings.getInt("HP_smoothing"); */ - } + //} //Get Band Power widget settings //FIX ME @@ -663,7 +675,7 @@ class SessionSettings { //Apply Time Series Settings Last!!! loadApplyTimeSeriesSettings(); - if (w_headPlot != null) { + //if (w_headPlot != null) { //FIX ME /* //Force headplot to redraw if it is active @@ -678,7 +690,7 @@ class SessionSettings { println("Headplot is active: Redrawing"); } */ - } + //} } //end of loadGUISettings ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -785,6 +797,9 @@ class SessionSettings { */ //FIX ME //w_spectrogram.cp5_widget.getController("SpectrogramLogLin").getCaptionLabel().setText(fftLogLinArray[spectLogLinLoad]); + + /* + //FIX ME try { //apply channel checkbox settings w_spectrogram.spectChanSelectTop.deactivateAllButtons(); @@ -803,7 +818,7 @@ class SessionSettings { println("Settings: Exception caught applying spectrogram settings channel bar " + e); } println("Settings: Spectrogram Active Channels: TOP - " + loadSpectActiveChanTop + " || BOT - " + loadSpectActiveChanBot); - + */ ///////////Apply Networking Settings String nwSettingsString = loadNetworkingSettings.toString(); dataProcessing.networkingSettings.loadJson(nwSettingsString); @@ -811,16 +826,21 @@ class SessionSettings { ////////////////////////////Apply EMG widget settings try { //apply channel checkbox settings + //FIX ME + /* w_emg.emgChannelSelect.deactivateAllButtons();; for (int i = 0; i < loadEmgActiveChannels.size(); i++) { w_emg.emgChannelSelect.setToggleState(loadEmgActiveChannels.get(i), true); } + */ } catch (Exception e) { println("Settings: Exception caught applying EMG widget settings " + e); } verbosePrint("Settings: EMG Widget Active Channels: " + loadEmgActiveChannels); ////////////////////////////Apply EMG Joystick settings + //FIX ME + /* w_emgJoystick.setJoystickSmoothing(loadEmgJoystickSmoothing); w_emgJoystick.cp5_widget.getController("emgJoystickSmoothingDropdown").getCaptionLabel() .setText(EmgJoystickSmoothing.getEnumStringsAsList().get(loadEmgJoystickSmoothing)); @@ -831,7 +851,10 @@ class SessionSettings { } catch (Exception e) { println("Settings: Exception caught applying EMG Joystick settings " + e); } - + */ + + //FIX ME + /* ////////////////////////////Apply Marker Widget settings w_marker.setMarkerWindow(loadMarkerWindow); w_marker.cp5_widget.getController("markerWindowDropdown").getCaptionLabel().setText(w_marker.getMarkerWindow().getString()); @@ -845,7 +868,7 @@ class SessionSettings { w_focus.cp5_widget.getController("focusThresholdDropdown").getCaptionLabel().setText(w_focus.getFocusThreshold().getString()); w_focus.setFocusHorizScale(loadFocusWindow); w_focus.cp5_widget.getController("focusWindowDropdown").getCaptionLabel().setText(w_focus.getFocusWindow().getString()); - + */ //////////////////////////////////////////////////////////// // Apply more loaded widget settings above this line // @@ -855,6 +878,8 @@ class SessionSettings { JSONObject loadTimeSeriesSettings = loadSettingsJSONData.getJSONObject(kJSONKeyTimeSeries); ////////Apply Time Series widget settings + //FIX ME + /* w_timeSeries.setVerticalScale(loadTimeSeriesSettings.getInt("Time Series Vert Scale")); w_timeSeries.cp5_widget.getController("VertScale_TS").getCaptionLabel().setText(w_timeSeries.getVerticalScale().getString()); //changes front-end @@ -874,7 +899,7 @@ class SessionSettings { println("Settings: Exception caught applying time series settings " + e); } verbosePrint("Settings: Time Series Active Channels: " + loadBPActiveChans); - + */ } //end loadApplyTimeSeriesSettings /** diff --git a/OpenBCI_GUI/SignalCheckThresholds.pde b/OpenBCI_GUI/SignalCheckThresholds.pde index 549f3023c..f24a9bea0 100644 --- a/OpenBCI_GUI/SignalCheckThresholds.pde +++ b/OpenBCI_GUI/SignalCheckThresholds.pde @@ -127,10 +127,11 @@ class SignalCheckThresholdUI { valuePercentage = val; } else { if (currentBoard instanceof BoardCyton) { + W_CytonImpedance cytonImpedanceWidget = (W_CytonImpedance) widgetManager.getWidget("W_CytonImpedance"); if (name == "errorThreshold") { - w_cytonImpedance.updateElectrodeStatusYellowThreshold((double)val); + cytonImpedanceWidget.updateElectrodeStatusYellowThreshold((double)val); } else { - w_cytonImpedance.updateElectrodeStatusGreenThreshold((double)val); + cytonImpedanceWidget.updateElectrodeStatusGreenThreshold((double)val); } } valuekOhms = val; diff --git a/OpenBCI_GUI/TopNav.pde b/OpenBCI_GUI/TopNav.pde index 1c839dc27..36be7ca7d 100644 --- a/OpenBCI_GUI/TopNav.pde +++ b/OpenBCI_GUI/TopNav.pde @@ -563,10 +563,10 @@ class TopNav { public void dataStreamTogglePressed() { //Exit method if doing Cyton impedance check. Avoids a BrainFlow error. - if (currentBoard instanceof BoardCyton && w_cytonImpedance != null) { + if (currentBoard instanceof BoardCyton && widgetManager.getWidgetExists("W_CytonImpedance")) { Integer checkingImpOnChan = ((ImpedanceSettingsBoard)currentBoard).isCheckingImpedanceOnChannel(); - //println("isCheckingImpedanceOnAnythingEZCHECK==",w_cytonImpedance.isCheckingImpedanceOnAnything); - if (checkingImpOnChan != null || w_cytonImpedance.cytonMasterImpedanceCheckIsActive() || w_cytonImpedance.isCheckingImpedanceOnAnything) { + W_CytonImpedance cytonImpedanceWidget = (W_CytonImpedance) widgetManager.getWidget("W_CytonImpedance"); + if (checkingImpOnChan != null || cytonImpedanceWidget.cytonMasterImpedanceCheckIsActive() || cytonImpedanceWidget.getIsCheckingImpedanceOnAnything()) { PopupMessage msg = new PopupMessage("Busy Checking Impedance", "Please turn off impedance check to begin recording the data stream."); println("OpenBCI_GUI::Cyton: Please turn off impedance check to begin recording the data stream."); return; diff --git a/OpenBCI_GUI/W_Accelerometer.pde b/OpenBCI_GUI/W_Accelerometer.pde index b9038e4db..470cdef4c 100644 --- a/OpenBCI_GUI/W_Accelerometer.pde +++ b/OpenBCI_GUI/W_Accelerometer.pde @@ -44,8 +44,8 @@ class W_Accelerometer extends Widget { private AccelerometerCapableBoard accelBoard; - W_Accelerometer(String _widgetName) { - super(_widgetName); + W_Accelerometer(String widgetName) { + super(widgetName); accelBoard = (AccelerometerCapableBoard)currentBoard; @@ -183,11 +183,11 @@ class W_Accelerometer extends Widget { output("Starting to read accelerometer"); accelModeButton.getCaptionLabel().setText("Turn Accel. Off"); if (currentBoard instanceof DigitalCapableBoard) { - w_digitalRead.toggleDigitalReadButton(false); + ((W_DigitalRead) widgetManager.getWidget("W_DigitalRead")).toggleDigitalReadButton(false); } if (currentBoard instanceof AnalogCapableBoard) { - w_pulseSensor.toggleAnalogReadButton(false); - w_analogRead.toggleAnalogReadButton(false); + ((W_PulseSensor) widgetManager.getWidget("W_PulseSensor")).toggleAnalogReadButton(false); + ((W_AnalogRead) widgetManager.getWidget("W_AnalogRead")).toggleAnalogReadButton(false); } ///Hide button when set On for Cyton board only. This is a special case for Cyton board Aux mode behavior. See BoardCyton.pde for more info. if ((currentBoard instanceof BoardCyton)) { @@ -276,11 +276,11 @@ class W_Accelerometer extends Widget { }; public void accelerometerVerticalScaleDropdown(int n) { - w_accelerometer.setVerticalScale(n); + ((W_Accelerometer) widgetManager.getWidget("W_Accelerometer")).setVerticalScale(n); } public void accelerometerHorizontalScaleDropdown(int n) { - w_accelerometer.setHorizontalScale(n); + ((W_Accelerometer) widgetManager.getWidget("W_Accelerometer")).setHorizontalScale(n); } //======================================================================================================================== diff --git a/OpenBCI_GUI/W_AnalogRead.pde b/OpenBCI_GUI/W_AnalogRead.pde index 804b9a354..9680cf931 100644 --- a/OpenBCI_GUI/W_AnalogRead.pde +++ b/OpenBCI_GUI/W_AnalogRead.pde @@ -131,16 +131,16 @@ class W_AnalogRead extends Widget { analogBoard.setAnalogActive(true); analogModeButton.getCaptionLabel().setText("Turn Analog Read Off"); output("Starting to read analog inputs on pin marked A5 (D11), A6 (D12) and A7 (D13)"); - w_pulseSensor.toggleAnalogReadButton(true); - w_accelerometer.accelBoardSetActive(false); - w_digitalRead.toggleDigitalReadButton(false); + ((W_PulseSensor) widgetManager.getWidget("W_Accelerometer")).toggleAnalogReadButton(true); + ((W_Accelerometer) widgetManager.getWidget("W_Accelerometer")).accelBoardSetActive(false); + ((W_DigitalRead) widgetManager.getWidget("W_Accelerometer")).toggleDigitalReadButton(false); } else { analogBoard.setAnalogActive(false); analogModeButton.getCaptionLabel().setText("Turn Analog Read On"); output("Starting to read accelerometer"); - w_accelerometer.accelBoardSetActive(true); - w_digitalRead.toggleDigitalReadButton(false); - w_pulseSensor.toggleAnalogReadButton(false); + ((W_Accelerometer) widgetManager.getWidget("W_Accelerometer")).accelBoardSetActive(true); + ((W_DigitalRead) widgetManager.getWidget("W_Accelerometer")).toggleDigitalReadButton(false); + ((W_PulseSensor) widgetManager.getWidget("W_Accelerometer")).toggleAnalogReadButton(false); } } }); @@ -174,11 +174,11 @@ class W_AnalogRead extends Widget { }; public void analogReadVerticalScaleDropdown(int n) { - w_analogRead.setVerticalScale(n); + ((W_AnalogRead) widgetManager.getWidget("W_AnalogRead")).setVerticalScale(n); } public void analogReadHorizontalScaleDropdown(int n) { - w_analogRead.setHorizontalScale(n); + ((W_AnalogRead) widgetManager.getWidget("W_AnalogRead")).setHorizontalScale(n); } //======================================================================================================================== diff --git a/OpenBCI_GUI/W_BandPower.pde b/OpenBCI_GUI/W_BandPower.pde index 0c185bdf2..ea9a6009f 100644 --- a/OpenBCI_GUI/W_BandPower.pde +++ b/OpenBCI_GUI/W_BandPower.pde @@ -268,25 +268,25 @@ class W_BandPower extends Widget { }; public void bandPowerAutoCleanDropdown(int n) { - w_bandPower.setAutoClean(n); + ((W_BandPower) widgetManager.getWidget("W_BandPower")).setAutoClean(n); } public void bandPowerAutoCleanThresholdDropdown(int n) { - w_bandPower.setAutoCleanThreshold(n); + ((W_BandPower) widgetManager.getWidget("W_BandPower")).setAutoCleanThreshold(n); } public void bandPowerAutoCleanTimerDropdown(int n) { - w_bandPower.setAutoCleanTimer(n); + ((W_BandPower) widgetManager.getWidget("W_BandPower")).setAutoCleanTimer(n); } public void bandPowerSmoothingDropdown(int n) { globalFFTSettings.setSmoothingFactor(FFTSmoothingFactor.values()[n]); FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); - w_fft.setSmoothingDropdownFrontend(smoothingFactor); + ((W_Fft) widgetManager.getWidget("W_Fft")).setSmoothingDropdownFrontend(smoothingFactor); } public void bandPowerDataFilteringDropdown(int n) { globalFFTSettings.setFilteredEnum(FFTFilteredEnum.values()[n]); FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); - w_fft.setFilteringDropdownFrontend(filteredEnum); + ((W_Fft) widgetManager.getWidget("W_Fft")).setFilteringDropdownFrontend(filteredEnum); } \ No newline at end of file diff --git a/OpenBCI_GUI/W_CytonImpedance.pde b/OpenBCI_GUI/W_CytonImpedance.pde index e6795f375..477e74f04 100644 --- a/OpenBCI_GUI/W_CytonImpedance.pde +++ b/OpenBCI_GUI/W_CytonImpedance.pde @@ -48,7 +48,7 @@ class W_CytonImpedance extends Widget { private int prevMasterCheckCounter = -1; private int numElectrodesToMasterCheck = 0; private int prevMasterCheckMillis = 0; //Used for simple timer - public boolean isCheckingImpedanceOnAnything = false; //This is more reliable than waiting to see if the Board is checking impedance + private boolean isCheckingImpedanceOnAnything = false; //This is more reliable than waiting to see if the Board is checking impedance private SignalCheckThresholdUI errorThreshold; private SignalCheckThresholdUI warningThreshold; @@ -297,8 +297,9 @@ class W_CytonImpedance extends Widget { cytonImpedanceMasterCheck.setOff(); } else if (signalCheckMode == CytonSignalCheckMode.IMPEDANCE) { //Attempt to close Hardware Settings view. Also, throws a popup if there are unsent changes. - if (w_timeSeries.getAdsSettingsVisible()) { - w_timeSeries.closeADSSettings(); + W_TimeSeries timeSeriesWidget = widgetManager.getTimeSeriesWidget(); + if (timeSeriesWidget.getAdsSettingsVisible()) { + timeSeriesWidget.closeADSSettings(); } //Clear the cells and show buttons instead for (int i = 1; i < numTableRows; i++) { @@ -512,8 +513,9 @@ class W_CytonImpedance extends Widget { && checkingOtherChan_isNpin.equals(e.getIsNPin())) { //println("TOGGLE OFF", e.getGUIChannelNumber(), e.getIsNPin(), "TOGGLE TO ==", false); e.overrideTestingButtonSwitch(false); - w_timeSeries.adsSettingsController.updateChanSettingsDropdowns(checkingOtherChan-1, cytonBoard.isEXGChannelActive(checkingOtherChan-1)); - w_timeSeries.adsSettingsController.setHasUnappliedSettings(checkingOtherChan-1, false); + W_TimeSeries timeSeriesWidget = widgetManager.getTimeSeriesWidget(); + timeSeriesWidget.adsSettingsController.updateChanSettingsDropdowns(checkingOtherChan-1, cytonBoard.isEXGChannelActive(checkingOtherChan-1)); + timeSeriesWidget.adsSettingsController.setHasUnappliedSettings(checkingOtherChan-1, false); } } @@ -532,8 +534,9 @@ class W_CytonImpedance extends Widget { cytonImpedanceMasterCheck.setOff(); } else { //If successful, update the front end components to reflect the new state - w_timeSeries.adsSettingsController.updateChanSettingsDropdowns(checkingChanX, cytonBoard.isEXGChannelActive(checkingChanX)); - w_timeSeries.adsSettingsController.setHasUnappliedSettings(checkingChanX, false); + W_TimeSeries timeSeriesWidget = widgetManager.getTimeSeriesWidget(); + timeSeriesWidget.adsSettingsController.updateChanSettingsDropdowns(checkingChanX, cytonBoard.isEXGChannelActive(checkingChanX)); + timeSeriesWidget.adsSettingsController.setHasUnappliedSettings(checkingChanX, false); } boolean shouldBeOn = toggle && response; @@ -685,7 +688,7 @@ class W_CytonImpedance extends Widget { // Update ADS1299 settings to default but don't commit. Instead, sent "d" command twice. cytonBoard.getADS1299Settings().revertAllChannelsToDefaultValues(); - w_timeSeries.adsSettingsController.updateAllChanSettingsDropdowns(); + widgetManager.getTimeSeriesWidget().adsSettingsController.updateAllChanSettingsDropdowns(); timeElapsed = millis() - timeElapsed; StringBuilder sb = new StringBuilder("Cyton Impedance Check: Hard reset to default board mode took -- "); @@ -730,18 +733,22 @@ class W_CytonImpedance extends Widget { cytonElectrodeStatus[i].updateYellowThreshold(_d); } } + + public boolean getIsCheckingImpedanceOnAnything() { + return isCheckingImpedanceOnAnything; + } }; //These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected //Update: It's not worth the trouble to implement a callback listener in the widget for this specifc kind of dropdown. Keep using this pattern for widget Nav dropdowns. - February 2021 RW void CytonImpedance_Mode(int n) { - w_cytonImpedance.setSignalCheckMode(n); + ((W_CytonImpedance) widgetManager.getWidget("W_CytonImpedance")).setSignalCheckMode(n); } void CytonImpedance_LabelMode(int n) { - w_cytonImpedance.setShowAnatomicalName(n); + ((W_CytonImpedance) widgetManager.getWidget("W_CytonImpedance")).setShowAnatomicalName(n); } void CytonImpedance_MasterCheckInterval(int n) { - w_cytonImpedance.setMasterCheckInterval(n); + ((W_CytonImpedance) widgetManager.getWidget("W_CytonImpedance")).setMasterCheckInterval(n); } \ No newline at end of file diff --git a/OpenBCI_GUI/W_DigitalRead.pde b/OpenBCI_GUI/W_DigitalRead.pde index d535cf69b..11cc081d9 100644 --- a/OpenBCI_GUI/W_DigitalRead.pde +++ b/OpenBCI_GUI/W_DigitalRead.pde @@ -146,16 +146,16 @@ class W_DigitalRead extends Widget { digitalBoard.setDigitalActive(true); digitalModeButton.getCaptionLabel().setText("Turn Digital Read Off"); output("Starting to read digital inputs on pin marked D11, D12, D13, D17 and D18"); - w_accelerometer.accelBoardSetActive(false); - w_analogRead.toggleAnalogReadButton(false); - w_pulseSensor.toggleAnalogReadButton(false); + ((W_Accelerometer) widgetManager.getWidget("W_Accelerometer")).accelBoardSetActive(false); + ((W_AnalogRead) widgetManager.getWidget("W_AnalogRead")).toggleAnalogReadButton(false); + ((W_PulseSensor) widgetManager.getWidget("W_Accelerometer")).toggleAnalogReadButton(false); } else { digitalBoard.setDigitalActive(false); digitalModeButton.getCaptionLabel().setText("Turn Digital Read On"); output("Starting to read accelerometer"); - w_accelerometer.accelBoardSetActive(true); - w_analogRead.toggleAnalogReadButton(false); - w_pulseSensor.toggleAnalogReadButton(false); + ((W_Accelerometer) widgetManager.getWidget("W_Accelerometer")).accelBoardSetActive(true); + ((W_AnalogRead) widgetManager.getWidget("W_AnalogRead")).toggleAnalogReadButton(false); + ((W_PulseSensor) widgetManager.getWidget("W_Accelerometer")).toggleAnalogReadButton(false); } } }); diff --git a/OpenBCI_GUI/W_EMG.pde b/OpenBCI_GUI/W_EMG.pde index 97ab38e9a..12ab81491 100644 --- a/OpenBCI_GUI/W_EMG.pde +++ b/OpenBCI_GUI/W_EMG.pde @@ -13,7 +13,7 @@ // TODO: Add dynamic threshold functionality //////////////////////////////////////////////////////////////////////////////// -class W_emg extends Widget { +class W_Emg extends Widget { private ControlP5 emgCp5; private Button emgSettingsButton; private final int EMG_SETTINGS_BUTTON_WIDTH = 125; @@ -21,7 +21,7 @@ class W_emg extends Widget { public ExGChannelSelect emgChannelSelect; - W_emg (String _widgetName) { + W_Emg (String _widgetName) { super(_widgetName); cp5ElementsToCheck = new ArrayList(); diff --git a/OpenBCI_GUI/W_EMGJoystick.pde b/OpenBCI_GUI/W_EMGJoystick.pde index 4f645dd7a..3d0ed8bea 100644 --- a/OpenBCI_GUI/W_EMGJoystick.pde +++ b/OpenBCI_GUI/W_EMGJoystick.pde @@ -9,7 +9,7 @@ // // ///////////////////////////////////////////////////////////////////////////////////////////////////////// -class W_EMGJoystick extends Widget { +class W_EmgJoystick extends Widget { private ControlP5 emgCp5; private Button emgSettingsButton; @@ -71,7 +71,7 @@ class W_EMGJoystick extends Widget { private PImage yPositiveInputLabelImage = loadImage("EMG_Joystick/UP_100x100.png"); private PImage yNegativeInputLabelImage = loadImage("EMG_Joystick/DOWN_100x100.png"); - W_EMGJoystick(String _widgetName) { + W_EmgJoystick(String _widgetName) { super(_widgetName); emgCp5 = new ControlP5(ourApplet); @@ -461,116 +461,5 @@ class W_EMGJoystick extends Widget { }; public void emgJoystickSmoothingDropdown(int n) { - w_emgJoystick.setJoystickSmoothing(n); -} - -public enum EmgJoystickSmoothing implements IndexingInterface -{ - OFF (0, "Off", 0f), - POINT_9 (1, "0.9", .9f), - POINT_95 (2, "0.95", .95f), - POINT_98 (3, "0.98", .98f), - POINT_99 (4, "0.99", .99f), - POINT_999 (5, "0.999", .999f), - POINT_9999 (6, "0.9999", .9999f); - - private int index; - private String name; - private float value; - private static EmgJoystickSmoothing[] vals = values(); - - EmgJoystickSmoothing(int index, String name, float value) { - this.index = index; - this.name = name; - this.value = value; - } - - public int getIndex() { - return index; - } - - public String getString() { - return name; - } - - public float getValue() { - return value; - } - - private static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } -} - -public class EMGJoystickInput { - private int index; - private String name; - private int value; - - EMGJoystickInput(int index, String name, int value) { - this.index = index; - this.name = name; - this.value = value; - } - - public int getIndex() { - return index; - } - - public String getString() { - return name; - } - - public int getValue() { - return value; - } -} - -public class EMGJoystickInputs { - private final int NUM_EMG_INPUTS = 4; - private final EMGJoystickInput[] VALUES; - private final EMGJoystickInput[] INPUTS = new EMGJoystickInput[NUM_EMG_INPUTS]; - - EMGJoystickInputs(int numExGChannels) { - VALUES = new EMGJoystickInput[numExGChannels]; - for (int i = 0; i < numExGChannels; i++) { - VALUES[i] = new EMGJoystickInput(i, "Channel " + (i + 1), i); - } - } - - public EMGJoystickInput[] getValues() { - return VALUES; - } - - public EMGJoystickInput[] getInputs() { - return INPUTS; - } - - public EMGJoystickInput getInput(int index) { - return INPUTS[index]; - } - - public void setInputToChannel(int inputNumber, int channel) { - if (inputNumber < 0 || inputNumber >= NUM_EMG_INPUTS) { - println("Invalid input number: " + inputNumber); - return; - } - if (channel < 0 || channel >= VALUES.length) { - println("Invalid channel: " + channel); - return; - } - INPUTS[inputNumber] = VALUES[channel]; - } - - public List getValueStringsAsList() { - List enumStrings = new ArrayList(); - for (EMGJoystickInput val : VALUES) { - enumStrings.add(val.getString()); - } - return enumStrings; - } + ((W_EmgJoystick) widgetManager.getWidget("W_EmgJoystick")).setJoystickSmoothing(n); } \ No newline at end of file diff --git a/OpenBCI_GUI/W_FFT.pde b/OpenBCI_GUI/W_FFT.pde index 68ddfaca7..80f478ad7 100644 --- a/OpenBCI_GUI/W_FFT.pde +++ b/OpenBCI_GUI/W_FFT.pde @@ -12,7 +12,7 @@ // /////////////////////////////////////////////////// -class W_fft extends Widget { +class W_Fft extends Widget { public ExGChannelSelect fftChanSelect; private boolean prevChanSelectIsVisible = false; @@ -28,7 +28,7 @@ class W_fft extends Widget { List cp5ElementsToCheck = new ArrayList(); - W_fft(String _widgetName) { + W_Fft(String _widgetName) { super(_widgetName); fftChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); @@ -212,25 +212,25 @@ class W_fft extends Widget { //These functions need to be global! These functions are activated when an item from the corresponding dropdown is selected public void fftMaxFrequencyDropdown(int n) { - w_fft.setMaxFrequency(n); + ((W_Fft) widgetManager.getWidget("W_Fft")).setMaxFrequency(n); } public void fftVerticalScaleDropdown(int n) { - w_fft.setVerticalScale(n); + ((W_Fft) widgetManager.getWidget("W_Fft")).setVerticalScale(n); } public void fftLogLinDropdown(int n) { - w_fft.setLogLin(n); + ((W_Fft) widgetManager.getWidget("W_Fft")).setLogLin(n); } public void fftSmoothingDropdown(int n) { globalFFTSettings.setSmoothingFactor(FFTSmoothingFactor.values()[n]); FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); - w_bandPower.setSmoothingDropdownFrontend(smoothingFactor); + ((W_BandPower) widgetManager.getWidget("W_BandPower")).setSmoothingDropdownFrontend(smoothingFactor); } public void fftFilteringDropdown(int n) { globalFFTSettings.setFilteredEnum(FFTFilteredEnum.values()[n]); FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); - w_bandPower.setFilteringDropdownFrontend(filteredEnum); + ((W_BandPower) widgetManager.getWidget("W_BandPower")).setFilteringDropdownFrontend(filteredEnum); } \ No newline at end of file diff --git a/OpenBCI_GUI/W_Focus.pde b/OpenBCI_GUI/W_Focus.pde index f051581f7..2d8d96956 100644 --- a/OpenBCI_GUI/W_Focus.pde +++ b/OpenBCI_GUI/W_Focus.pde @@ -416,13 +416,13 @@ class W_Focus extends Widget { //The following global functions are used by the Focus widget dropdowns. This method is the least amount of code. public void focusWindowDropdown(int n) { - w_focus.setFocusHorizScale(n); + ((W_Focus) widgetManager.getWidget("W_Focus")).setFocusHorizScale(n); } public void focusMetricDropdown(int n) { - w_focus.setMetric(n); + ((W_Focus) widgetManager.getWidget("W_Focus")).setMetric(n); } public void focusThresholdDropdown(int n) { - w_focus.setThreshold(n); + ((W_Focus) widgetManager.getWidget("W_Focus")).setThreshold(n); } diff --git a/OpenBCI_GUI/W_GanglionImpedance.pde b/OpenBCI_GUI/W_GanglionImpedance.pde index 4d0cd7600..b8193aedf 100644 --- a/OpenBCI_GUI/W_GanglionImpedance.pde +++ b/OpenBCI_GUI/W_GanglionImpedance.pde @@ -1,16 +1,3 @@ - -//////////////////////////////////////////////////// -// -// W_template.pde (ie "Widget Template") -// -// This is a Template Widget, intended to be used as a starting point for OpenBCI Community members that want to develop their own custom widgets! -// Good luck! If you embark on this journey, please let us know. Your contributions are valuable to everyone! -// -// Created by: Conor Russomanno, November 2016 -// -///////////////////////////////////////////////////, - - class W_GanglionImpedance extends Widget { Button startStopCheck; int padding = 24; diff --git a/OpenBCI_GUI/W_HeadPlot.pde b/OpenBCI_GUI/W_HeadPlot.pde index f3b8f78e0..5495d82ea 100644 --- a/OpenBCI_GUI/W_HeadPlot.pde +++ b/OpenBCI_GUI/W_HeadPlot.pde @@ -1,21 +1,13 @@ +//////////////////////////////////////////////////////////// +// // +// W_HeadPlot.pde // +// Created by: Conor Russomanno, November 2016 // +// Based on code written by: Chip Audette, Oct 2013 // +// Refactored by: Richard Waltman, April 2025 // +// // +//////////////////////////////////////////////////////////// -//////////////////////////////////////////////////// -// -// W_template.pde (ie "Widget Template") -// -// This is a Template Widget, intended to be used as a starting point for OpenBCI Community members that want to develop their own custom widgets! -// Good luck! If you embark on this journey, please let us know. Your contributions are valuable to everyone! -// -// Created by: Conor Russomanno, November 2016 -// Based on code written by: Chip Audette, Oct 2013 -// Refactored by: Richard Waltman, March 2025 -// -///////////////////////////////////////////////////, - - -// ----- these variable/methods are used for adjusting the intensity factor of the headplot opacity --------------------------------------------------------------------------------------------------------- -//float default_vertScale_uV = 200.0; -//float[] vertScaleFactor = { 0.25f, 0.5f, 1.0f, 2.0f, 5.0f, 50.0f}; +import java.util.concurrent.locks.ReentrantLock; class W_HeadPlot extends Widget { @@ -64,7 +56,7 @@ class W_HeadPlot extends Widget { headPlot.hp_win_x = x; headPlot.hp_win_y = y; - thread("doHardCalculations"); + headPlot.setPositionSize(x, y, w, h, x, y); } public void mousePressed(){ @@ -102,31 +94,22 @@ class W_HeadPlot extends Widget { headPlotSmoothing = HeadPlotSmoothing.values[n]; headPlot.smoothingFactor = headPlotSmoothing.getValue(); } - - private void doHardCalculations() { - if (!headPlot.threadLock) { - headPlot.threadLock = true; - headPlot.setPositionSize(w_headPlot.headPlot.hp_x, w_headPlot.headPlot.hp_y, w_headPlot.headPlot.hp_w, w_headPlot.headPlot.hp_h, w_headPlot.headPlot.hp_win_x, w_headPlot.headPlot.hp_win_y); - headPlot.hardCalcsDone = true; - headPlot.threadLock = false; - } - } }; public void headPlotIntensityDropdown(int n) { - w_headPlot.setIntensity(n); + ((W_HeadPlot) widgetManager.getWidget("W_HeadPlot")).setIntensity(n); } public void headPlotPolarityDropdown(int n) { - w_headPlot.setPolarity(n); + ((W_HeadPlot) widgetManager.getWidget("W_HeadPlot")).setPolarity(n); } public void headPlotContoursDropdown(int n) { - w_headPlot.setContours(n); + ((W_HeadPlot) widgetManager.getWidget("W_HeadPlot")).setContours(n); } public void headPlotSmoothingDropdown(int n) { - w_headPlot.setSmoothing(n); + ((W_HeadPlot) widgetManager.getWidget("W_HeadPlot")).setSmoothing(n); } //--------------------------------------------------------------------------------------------------------------------------------------- @@ -180,8 +163,8 @@ class HeadPlot { public int hp_y = 0; public int hp_w = 0; public int hp_h = 0; - public boolean hardCalcsDone = false; - public boolean threadLock = false; + private final ReentrantLock lock = new ReentrantLock(); + private final AtomicBoolean hardCalculationsDone = new AtomicBoolean(false); HeadPlot(int _x, int _y, int _w, int _h, int _win_x, int _win_y) { final int n_elec = globalChannelCount; //set number of electrodes using the global globalChannelCount variable @@ -299,11 +282,13 @@ class HeadPlot { image_x = int(round(circ_x - 0.5*circ_diam - 0.5*ear_width)); image_y = nose_y[2]; headImage = createImage(int(total_width), int(total_height), ARGB); + headVoltage = new float[int(total_width)][int(total_height)]; // Initialize headVoltage here //initialize the image for (int Iy=0; Iy < headImage.height; Iy++) { for (int Ix = 0; Ix < headImage.width; Ix++) { headImage.set(Ix, Iy, WHITE); + headVoltage[Ix][Iy] = 0.0f; // Initialize with default values } } @@ -970,7 +955,6 @@ class HeadPlot { //it is inside the head. set the color based on the electrodes headImage.set(Ix, Iy, calcPixelColor(Ix, Iy)); } else { //negative values are outside of the head - //pixel is outside the head. set to black. headImage.set(Ix, Iy, WHITE); } } @@ -978,6 +962,10 @@ class HeadPlot { } private void convertVoltagesToHeadImage() { + if (headImage == null || electrode_color_weightFac == null || headVoltage == null) { + println("ERROR: HeadPlot data structures not initialized"); + return; + } for (int Iy=0; Iy < headImage.height; Iy++) { for (int Ix = 0; Ix < headImage.width; Ix++) { //is this pixel inside the head? @@ -1176,27 +1164,23 @@ class HeadPlot { public void update() { //do this when new data is available - if (!hardCalcsDone) { - thread("doHardCalcs"); + if (!hardCalculationsDone.get()) { + setPositionSize(hp_x, hp_y, hp_w, hp_h, hp_win_x, hp_win_y); + hardCalculationsDone.set(true); } //update electrode colors updateElectrodeColors(); - if (false) { - //update the head image - if (drawHeadAsContours) updateHeadImage(); - } else { - //update head voltages - if (!threadLock && hardCalcsDone) { - convertVoltagesToHeadImage(); - } + //update head voltages + if (hardCalculationsDone.get() && headVoltage != null) { + convertVoltagesToHeadImage(); } } public void draw() { - if (!hardCalcsDone) { + if (!hardCalculationsDone.get() || headImage == null) { return; } @@ -1255,4 +1239,17 @@ class HeadPlot { popStyle(); } //end of draw method + + private void doHardCalculations() { + if (hardCalculationsDone.get()) return; + + lock.lock(); + try { + if (hardCalculationsDone.compareAndSet(false, true)) { + setPositionSize(hp_x, hp_y, hp_w, hp_h, hp_win_x, hp_win_y); + } + } finally { + lock.unlock(); + } + } }; diff --git a/OpenBCI_GUI/W_Marker.pde b/OpenBCI_GUI/W_Marker.pde index ff1d8abb9..af02a5507 100644 --- a/OpenBCI_GUI/W_Marker.pde +++ b/OpenBCI_GUI/W_Marker.pde @@ -595,11 +595,13 @@ public enum MarkerVertScale implements IndexingInterface //The following global functions are used by the Marker widget dropdowns. This method is the least amount of code. public void markerWindowDropdown(int n) { - w_marker.setMarkerWindow(n); + W_Marker markerWidget = (W_Marker) widgetManager.getWidget("W_Marker"); + markerWidget.setMarkerWindow(n); } public void markerVertScaleDropdown(int n) { - w_marker.setMarkerVertScale(n); + W_Marker markerWidget = (W_Marker) widgetManager.getWidget("W_Marker"); + markerWidget.setMarkerVertScale(n); } //Custom UDP receive handler for receiving markers from external sources @@ -607,10 +609,6 @@ public void receiveMarkerViaUdp( byte[] data, String ip, int port ) { double markerValue = convertByteArrayToDouble(data); //String message = Double.toString(markerValue); //println( "received: \""+message+"\" from "+ip+" on port "+port ); - w_marker.insertMarkerFromExternal(markerValue); + W_Marker markerWidget = (W_Marker) widgetManager.getWidget("W_Marker"); + markerWidget.insertMarkerFromExternal(markerValue); } - -public double convertByteArrayToDouble(byte[] array) { - ByteBuffer buffer = ByteBuffer.wrap(array); - return buffer.getDouble(); -} \ No newline at end of file diff --git a/OpenBCI_GUI/W_PulseSensor.pde b/OpenBCI_GUI/W_PulseSensor.pde index 502b131b1..4be2472e6 100644 --- a/OpenBCI_GUI/W_PulseSensor.pde +++ b/OpenBCI_GUI/W_PulseSensor.pde @@ -143,16 +143,16 @@ class W_PulseSensor extends Widget { analogBoard.setAnalogActive(true); analogModeButton.getCaptionLabel().setText("Turn Analog Read Off"); output("Starting to read analog inputs on pin marked D11."); - w_analogRead.toggleAnalogReadButton(true); - w_accelerometer.accelBoardSetActive(false); - w_digitalRead.toggleDigitalReadButton(false); + ((W_AnalogRead) widgetManager.getWidget("W_AnalogRead")).toggleAnalogReadButton(true); + ((W_Accelerometer) widgetManager.getWidget("W_Accelerometer")).accelBoardSetActive(false); + ((W_DigitalRead) widgetManager.getWidget("W_Accelerometer")).toggleDigitalReadButton(false); } else { analogBoard.setAnalogActive(false); analogModeButton.getCaptionLabel().setText("Turn Analog Read On"); output("Starting to read accelerometer"); - w_analogRead.toggleAnalogReadButton(false); - w_accelerometer.accelBoardSetActive(true); - w_digitalRead.toggleDigitalReadButton(false); + ((W_AnalogRead) widgetManager.getWidget("W_AnalogRead")).toggleAnalogReadButton(false); + ((W_Accelerometer) widgetManager.getWidget("W_Accelerometer")).accelBoardSetActive(true); + ((W_DigitalRead) widgetManager.getWidget("W_Accelerometer")).toggleDigitalReadButton(false); } } }); diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index 071517611..3dbb82638 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -427,7 +427,7 @@ class W_Spectrogram extends Widget { public void clear() { // Set all pixels to black (or any other background color you want to clear with) - for (int i = 0; i < w_spectrogram.dataImg.pixels.length; i++) { + for (int i = 0; i < dataImg.pixels.length; i++) { dataImg.pixels[i] = color(0); // Black background } } @@ -455,13 +455,13 @@ class W_Spectrogram extends Widget { }; public void spectrogramMaxFrequencyDropdown(int n) { - w_spectrogram.setMaxFrequency(n); + ((W_Spectrogram) widgetManager.getWidget("W_Spectrogram")).setMaxFrequency(n); } public void spectrogramWindowDropdown(int n) { - w_spectrogram.setWindowSize(n); + ((W_Spectrogram) widgetManager.getWidget("W_Spectrogram")).setWindowSize(n); } public void spectrogramLogLinDropdown(int n) { - w_spectrogram.setLogLin(n); + ((W_Spectrogram) widgetManager.getWidget("W_Spectrogram")).setLogLin(n); } \ No newline at end of file diff --git a/OpenBCI_GUI/W_Template.pde b/OpenBCI_GUI/W_Template.pde index c8c299fae..fa01ef737 100644 --- a/OpenBCI_GUI/W_Template.pde +++ b/OpenBCI_GUI/W_Template.pde @@ -1,7 +1,7 @@ //////////////////////////////////////////////////// // -// W_template.pde (ie "Widget Template") +// W_Template.pde (ie "Widget Template") // // This is a Template Widget, intended to be used as a starting point for OpenBCI Community members that want to develop their own custom widgets! // Good luck! If you embark on this journey, please let us know. Your contributions are valuable to everyone! @@ -10,14 +10,14 @@ // ///////////////////////////////////////////////////, -class W_template extends Widget { +class W_Template extends Widget { //to see all core variables/methods of the Widget class, refer to Widget.pde //put your custom variables here... ControlP5 localCP5; Button widgetTemplateButton; - W_template(String _widgetName) { + W_Template(String _widgetName) { super(_widgetName); //This is the protocol for setting up dropdowns. diff --git a/OpenBCI_GUI/W_TimeSeries.pde b/OpenBCI_GUI/W_TimeSeries.pde index fd9baf96d..2cf69ed1a 100644 --- a/OpenBCI_GUI/W_TimeSeries.pde +++ b/OpenBCI_GUI/W_TimeSeries.pde @@ -11,7 +11,7 @@ import org.apache.commons.lang3.math.NumberUtils; -class W_timeSeries extends Widget { +class W_TimeSeries extends Widget { //to see all core variables/methods of the Widget class, refer to Widget.pde //put your custom variables here... private int numChannelBars; @@ -51,7 +51,7 @@ class W_timeSeries extends Widget { List cp5ElementsToCheck = new ArrayList(); - W_timeSeries(String _widgetName) { + W_TimeSeries(String _widgetName) { super(_widgetName); tscp5 = new ControlP5(ourApplet); @@ -361,15 +361,15 @@ class W_timeSeries extends Widget { //These functions are activated when an item from the corresponding dropdown is selected void timeSeriesVerticalScaleDropdown(int n) { - w_timeSeries.setVerticalScale(n); + widgetManager.getTimeSeriesWidget().setVerticalScale(n); } void timeSeriesHorizontalScaleDropdown(int n) { - w_timeSeries.setHorizontalScale(n); + widgetManager.getTimeSeriesWidget().setHorizontalScale(n); } void LabelMode_TS(int n) { - w_timeSeries.setLabelMode(n); + widgetManager.getTimeSeriesWidget().setLabelMode(n); } //======================================================================================================================== @@ -609,7 +609,7 @@ class ChannelBar { pushStyle(); stroke(OPENBCI_DARKBLUE); strokeWeight(1); - int separator_y = y + h + int(w_timeSeries.INTER_CHANNEL_BAR_SPACE/2); + int separator_y = y + h + int(widgetManager.getTimeSeriesWidget().INTER_CHANNEL_BAR_SPACE / 2); line(x, separator_y, x + w, separator_y); popStyle(); } @@ -717,8 +717,8 @@ class ChannelBar { } private boolean isBottomChannel() { - int numActiveChannels = w_timeSeries.tsChanSelect.getActiveChannels().size(); - boolean isLastChannel = channelIndex == w_timeSeries.tsChanSelect.getActiveChannels().get(numActiveChannels - 1); + int numActiveChannels = widgetManager.getTimeSeriesWidget().tsChanSelect.getActiveChannels().size(); + boolean isLastChannel = channelIndex == widgetManager.getTimeSeriesWidget().tsChanSelect.getActiveChannels().get(numActiveChannels - 1); return isLastChannel; } @@ -737,9 +737,10 @@ class ChannelBar { println("[" + channelString + "] onOff released - " + (newState ? "On" : "Off")); currentBoard.setEXGChannelActive(channelIndex, newState); if (currentBoard instanceof ADS1299SettingsBoard) { - w_timeSeries.adsSettingsController.updateChanSettingsDropdowns(channelIndex, currentBoard.isEXGChannelActive(channelIndex)); + W_TimeSeries timeSeriesWidget = widgetManager.getTimeSeriesWidget(); + timeSeriesWidget.adsSettingsController.updateChanSettingsDropdowns(channelIndex, currentBoard.isEXGChannelActive(channelIndex)); boolean hasUnappliedChanges = currentBoard.isEXGChannelActive(channelIndex) != newState; - w_timeSeries.adsSettingsController.setHasUnappliedSettings(channelIndex, hasUnappliedChanges); + timeSeriesWidget.adsSettingsController.setHasUnappliedSettings(channelIndex, hasUnappliedChanges); } } }); diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index 85ca593c6..1901a6a3f 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -14,6 +14,7 @@ interface IndexingInterface { } class Widget { + protected String widgetName = "Widget"; //default name of the widget protected int x0, y0, w0, h0; //true x,y,w,h of container protected int x, y, w, h; //adjusted x,y,w,h of white space `blank rectangle` under the nav... diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index 32585c636..c59be1aa9 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -3,42 +3,20 @@ //======================================================================================== /* Notes: - - In this file all you have to do is MAKE YOUR WIDGET GLOBALLY, and then ADD YOUR WIDGET TO WIDGETS OF WIDGETMANAGER in the setupWidgets() function below - - the order in which they are added will effect the order in which they appear in the GUI and in the WidgetSelector dropdown menu of each widget - - use the WidgetTemplate.pde file as a starting point for creating new widgets (also check out W_timeSeries.pde, W_fft.pde, and W_HeadPlot.pde) + - The order in which they are added will effect the order in which they appear in the GUI and in the WidgetSelector dropdown menu of each widget + - Use the WidgetTemplate.pde file as a starting point for creating new widgets (also check out W_TimeSeries.pde, W_Fft.pde, and W_HeadPlot.pde) */ -// MAKE YOUR WIDGET GLOBAL HERE -W_timeSeries w_timeSeries; -W_fft w_fft; -W_BandPower w_bandPower; -W_Accelerometer w_accelerometer; -W_CytonImpedance w_cytonImpedance; -W_GanglionImpedance w_ganglionImpedance; -W_HeadPlot w_headPlot; -W_template w_template; -W_emg w_emg; -W_PulseSensor w_pulseSensor; -W_AnalogRead w_analogRead; -W_DigitalRead w_digitalRead; -W_playback w_playback; -W_Spectrogram w_spectrogram; -W_Focus w_focus; -W_EMGJoystick w_emgJoystick; -W_Marker w_marker; -W_PacketLoss w_packetLoss; - //======================================================================================== //======================================================================================== //======================================================================================== class WidgetManager{ - - //this holds all of the widgets ... when creating/adding new widgets, we will add them to this ArrayList (below) - ArrayList widgets; - ArrayList widgetOptions; //List of Widget Titles, used to populate cp5 widgetSelector dropdown of all widgets - int currentContainerLayout; //this is the Layout structure for the main body of the GUI ... refer to [PUT_LINK_HERE] for layouts/numbers image - ArrayList layouts = new ArrayList(); //this holds all of the different layouts ... + //This holds all of the widgets. When creating/adding new widgets, we will add them to this ArrayList (below) + private ArrayList widgets; + private ArrayList widgetOptions; //List of Widget Titles, used to populate cp5 widgetSelector dropdown of all widgets + private int currentContainerLayout; //This is the Layout structure for the main body of the GUI + private ArrayList layouts = new ArrayList(); //This holds all of the different layouts ... private boolean visible = true; @@ -64,88 +42,58 @@ class WidgetManager{ void setupWidgets() { - w_timeSeries = new W_timeSeries("Time Series"); - widgets.add(w_timeSeries); + widgets.add(new W_TimeSeries("Time Series")); - w_fft = new W_fft("FFT Plot"); - widgets.add(w_fft); + widgets.add(new W_Fft("FFT Plot")); - boolean showAccelerometerWidget = currentBoard instanceof AccelerometerCapableBoard; - if (showAccelerometerWidget) { - w_accelerometer = new W_Accelerometer("Accelerometer"); - widgets.add(w_accelerometer); + if (currentBoard instanceof AccelerometerCapableBoard) { + widgets.add(new W_Accelerometer("Accelerometer")); } if (currentBoard instanceof BoardCyton) { - w_cytonImpedance = new W_CytonImpedance("Cyton Signal"); - widgets.add(w_cytonImpedance); + widgets.add(new W_CytonImpedance("Cyton Signal")); } - if (currentBoard instanceof DataSourcePlayback && w_playback == null) { - w_playback = new W_playback("Playback History"); - widgets.add(w_playback); + if (currentBoard instanceof DataSourcePlayback) { + widgets.add(new W_playback("Playback History")); } - //only instantiate this widget if you are using a Ganglion board for live streaming if(globalChannelCount == 4 && currentBoard instanceof BoardGanglion){ - //If using Ganglion, this is Widget_3 - w_ganglionImpedance = new W_GanglionImpedance("Ganglion Signal"); - widgets.add(w_ganglionImpedance); + widgets.add(new W_GanglionImpedance("Ganglion Signal")); } - w_focus = new W_Focus("Focus"); - widgets.add(w_focus); + widgets.add(new W_Focus("Focus")); - w_bandPower = new W_BandPower("Band Power"); - widgets.add(w_bandPower); + widgets.add(new W_BandPower("Band Power")); - w_headPlot = new W_HeadPlot("Head Plot"); - widgets.add(w_headPlot); + widgets.add(new W_HeadPlot("Head Plot")); - w_emg = new W_emg("EMG"); - widgets.add(w_emg); + widgets.add(new W_Emg("EMG")); - w_emgJoystick = new W_EMGJoystick("EMG Joystick"); - widgets.add(w_emgJoystick); + widgets.add(new W_EmgJoystick("EMG Joystick")); - w_spectrogram = new W_Spectrogram("Spectrogram"); - widgets.add(w_spectrogram); + widgets.add(new W_Spectrogram("Spectrogram")); if (currentBoard instanceof AnalogCapableBoard){ - w_pulseSensor = new W_PulseSensor("Pulse Sensor"); - widgets.add(w_pulseSensor); + widgets.add(new W_PulseSensor("Pulse Sensor")); } if (currentBoard instanceof DigitalCapableBoard) { - w_digitalRead = new W_DigitalRead("Digital Read"); - widgets.add(w_digitalRead); + widgets.add(new W_DigitalRead("Digital Read")); } if (currentBoard instanceof AnalogCapableBoard) { - w_analogRead = new W_AnalogRead("Analog Read"); - widgets.add(w_analogRead); + widgets.add(new W_AnalogRead("Analog Read")); } if (currentBoard instanceof Board) { - w_packetLoss = new W_PacketLoss("Packet Loss"); - widgets.add(w_packetLoss); + widgets.add(new W_PacketLoss("Packet Loss")); } - w_marker = new W_Marker("Marker"); - widgets.add(w_marker); + widgets.add(new W_Marker("Marker")); //DEVELOPERS: Here is an example widget with the essentials/structure in place - w_template = new W_template("Widget Template"); - widgets.add(w_template); - } - - - public boolean isVisible() { - return visible; - } - - public void setVisible(boolean _visible) { - visible = _visible; + widgets.add(new W_Template("Widget Template")); } void setupWidgetSelectorDropdowns(){ @@ -162,29 +110,25 @@ class WidgetManager{ } } - void update(){ - if(visible){ - for(int i = 0; i < widgets.size(); i++){ - if(widgets.get(i).getIsActive()){ - widgets.get(i).update(); - //if the widgets are not mapped to containers correctly, remap them.. - // if(widgets.get(i).x != container[widgets.get(i).currentContainer].x || widgets.get(i).y != container[widgets.get(i).currentContainer].y || widgets.get(i).w != container[widgets.get(i).currentContainer].w || widgets.get(i).h != container[widgets.get(i).currentContainer].h){ - if(widgets.get(i).x0 != (int)container[widgets.get(i).currentContainer].x || widgets.get(i).y0 != (int)container[widgets.get(i).currentContainer].y || widgets.get(i).w0 != (int)container[widgets.get(i).currentContainer].w || widgets.get(i).h0 != (int)container[widgets.get(i).currentContainer].h){ - screenResized(); - println("WidgetManager.pde: Remapping widgets to container layout..."); - } + void update() { + for(int i = 0; i < widgets.size(); i++){ + if(widgets.get(i).getIsActive()){ + widgets.get(i).update(); + //if the widgets are not mapped to containers correctly, remap them.. + // if(widgets.get(i).x != container[widgets.get(i).currentContainer].x || widgets.get(i).y != container[widgets.get(i).currentContainer].y || widgets.get(i).w != container[widgets.get(i).currentContainer].w || widgets.get(i).h != container[widgets.get(i).currentContainer].h){ + if(widgets.get(i).x0 != (int)container[widgets.get(i).currentContainer].x || widgets.get(i).y0 != (int)container[widgets.get(i).currentContainer].y || widgets.get(i).w0 != (int)container[widgets.get(i).currentContainer].w || widgets.get(i).h0 != (int)container[widgets.get(i).currentContainer].h){ + screenResized(); + println("WidgetManager.pde: Remapping widgets to container layout..."); } } } } void draw(){ - if(visible){ - for(int i = 0; i < widgets.size(); i++){ - if(widgets.get(i).getIsActive()){ - widgets.get(i).draw(); - widgets.get(i).drawDropdowns(); - } + for(int i = 0; i < widgets.size(); i++){ + if(widgets.get(i).getIsActive()){ + widgets.get(i).draw(); + widgets.get(i).drawDropdowns(); } } } @@ -326,25 +270,6 @@ class WidgetManager{ public void setAllWidgetsNull() { widgets.clear(); - w_timeSeries = null; - w_fft = null; - w_bandPower = null; - w_accelerometer = null; - w_cytonImpedance = null; - w_ganglionImpedance = null; - w_headPlot = null; - w_template = null; - w_emg = null; - w_pulseSensor = null; - w_analogRead = null; - w_digitalRead = null; - w_playback = null; - w_spectrogram = null; - w_packetLoss = null; - w_focus = null; - w_emgJoystick = null; - w_marker = null; - println("Widget Manager: All widgets set to null."); } @@ -358,30 +283,27 @@ class WidgetManager{ } } } -}; - -//the Layout class is an orgnanizational tool ... a layout consists of a combination of containers ... refer to Container.pde -class Layout{ - Container[] myContainers; - int[] containerInts; - - Layout(int[] _myContainers){ //when creating a new layout, you pass in the integer #s of the containers you want as part of the layout ... so if I pass in the array {5}, my layout is 1 container that takes up the whole GUI body - //constructor stuff - myContainers = new Container[_myContainers.length]; //make the myContainers array equal to the size of the incoming array of ints - containerInts = new int[_myContainers.length]; - for(int i = 0; i < _myContainers.length; i++){ - myContainers[i] = container[_myContainers[i]]; - containerInts[i] = _myContainers[i]; + public Widget getWidget(String className) { + for (int i = 0; i < widgets.size(); i++) { + Widget widget = widgets.get(i); + // Get the class name of the widget + String widgetClassName = widget.getClass().getSimpleName(); + // Check if it matches the requested class name + if (widgetClassName.equals(className)) { + return widget; + } } + // Return null if no widget of the specified class is found + return null; } - Container getContainer(int _numContainer){ - if(_numContainer < myContainers.length){ - return myContainers[_numContainer]; - } else{ - println("Widget Manager: Tried to return a non-existant container..."); - return myContainers[myContainers.length-1]; - } + public boolean getWidgetExists(String className) { + Widget widget = getWidget(className); + return widget != null; + } + + public W_TimeSeries getTimeSeriesWidget() { + return (W_TimeSeries) getWidget("W_TimeSeries"); } }; \ No newline at end of file diff --git a/OpenBCI_GUI/WidgetSettings.pde b/OpenBCI_GUI/WidgetSettings.pde new file mode 100644 index 000000000..df5db7a13 --- /dev/null +++ b/OpenBCI_GUI/WidgetSettings.pde @@ -0,0 +1,166 @@ +class WidgetSettings { + // A map to store settings with string keys and enum values + protected HashMap> settings = new HashMap>(); + // Widget identifier for saving/loading specific widget settings + private String widgetName; + + public WidgetSettings(String widgetName) { + this.widgetName = widgetName; + } + + // Store a setting with a key and enum value + public > void setSetting(String key, T value) { + settings.put(key, value); + } + + // Retrieve a setting by key, with optional default value + public > T getSetting(String key, T defaultValue) { + if (settings.containsKey(key) && settings.get(key).getClass() == defaultValue.getClass()) { + return (T) settings.get(key); + } + return defaultValue; + } + + /** + * Converts settings to a JSON string + * @return JSON string representation of the settings + */ + public String getJSON() { + try { + // Create a wrapper JSON object that contains metadata and settings + JSONObject jsonData = new JSONObject(); + jsonData.setString("widgetName", widgetName); + + // Create settings JSON object + JSONObject settingsJson = new JSONObject(); + + // Add each setting to the JSON object with its class and value for proper deserialization + for (Map.Entry> entry : settings.entrySet()) { + JSONObject enumValue = new JSONObject(); + Enum value = entry.getValue(); + enumValue.setString("enumClass", value.getClass().getName()); + enumValue.setString("enumValue", value.name()); + settingsJson.setJSONObject(entry.getKey(), enumValue); + } + + jsonData.setJSONObject("settings", settingsJson); + return jsonData.toString(); + } catch (Exception e) { + println("Error converting settings to JSON: " + e.getMessage()); + e.printStackTrace(); + return "{}"; + } + } + + /** + * Loads settings from a JSON string + * @param jsonString JSON string to load settings from + * @return true if successful, false otherwise + */ + public boolean loadJSON(String jsonString) { + try { + // Parse the JSON string + JSONObject jsonData = parseJSONObject(jsonString); + if (jsonData == null) { + println("Invalid JSON string"); + return false; + } + + // Verify widget name + String loadedWidgetName = jsonData.getString("widgetName", ""); + if (!loadedWidgetName.equals(widgetName)) { + println("Warning: Widget name mismatch. Expected: " + widgetName + ", Found: " + loadedWidgetName); + // Continuing anyway, might be a compatible widget + } + + // Clear existing settings + settings.clear(); + + // Load settings + JSONObject settingsJson = jsonData.getJSONObject("settings"); + if (settingsJson == null) { + println("No settings found in JSON"); + return false; + } + + // Loop through each setting in the JSON + for (Object key : settingsJson.keys()) { + String settingKey = (String)key; + JSONObject enumData = settingsJson.getJSONObject(settingKey); + + String enumClassName = enumData.getString("enumClass", ""); + String enumValueName = enumData.getString("enumValue", ""); + + // Skip if missing required data + if (enumClassName.isEmpty() || enumValueName.isEmpty()) { + continue; + } + + try { + // Load the enum class + Class enumClass = Class.forName(enumClassName); + if (!enumClass.isEnum()) { + println("Class " + enumClassName + " is not an enum"); + continue; + } + + // Get the enum value + @SuppressWarnings("unchecked") + Enum enumValue = Enum.valueOf((Class)enumClass, enumValueName); + settings.put(settingKey, enumValue); + } catch (ClassNotFoundException e) { + println("Enum class not found: " + enumClassName); + } catch (IllegalArgumentException e) { + println("Enum value not found: " + enumValueName); + } catch (Exception e) { + println("Error loading enum: " + e.getMessage()); + } + } + + return true; + } catch (Exception e) { + println("Error loading settings from JSON: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + // Apply settings to UI components + public void applySettingsToCp5(ControlP5 cp5) { + // This is a default implementation + // Widget-specific classes should override this method + } +} + +// Example extension of WidgetSettings for a specific widget +class ExampleWidgetSettings extends WidgetSettings { + public ExampleWidgetSettings() { + super("ExampleWidget"); + } + + // Override to implement specific UI binding + @Override + public void applySettingsToCp5(ControlP5 cp5) { + // Example: Apply dropdown settings + for (String key : settings.keySet()) { + Enum value = settings.get(key); + + if (value instanceof IndexingInterface) { + IndexingInterface enumValue = (IndexingInterface)value; + ScrollableList dropdown = (ScrollableList)cp5.getController(key); + if (dropdown != null) { + dropdown.setValue(enumValue.getIndex()); + } + } + + // Handle other control types as needed + // Toggle, RadioButton, Slider, etc. + } + } + + // Additional methods specific to this widget + public void setupDefaultSettings() { + // Set default values for this widget + // Example: setSetting("mode", SomeEnum.DEFAULT_MODE); + } +} \ No newline at end of file From 73e392611a9a43e3d0d1d3ac545606ce8c47009f Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 10 Apr 2025 21:41:07 -0500 Subject: [PATCH 10/29] Remove widgetName from the constructor of base Widget class --- OpenBCI_GUI/W_Accelerometer.pde | 5 +- OpenBCI_GUI/W_AnalogRead.pde | 6 +- OpenBCI_GUI/W_BandPower.pde | 6 +- OpenBCI_GUI/W_CytonImpedance.pde | 5 +- OpenBCI_GUI/W_DigitalRead.pde | 5 +- OpenBCI_GUI/W_EMG.pde | 5 +- OpenBCI_GUI/W_EMGJoystick.pde | 6 +- OpenBCI_GUI/W_FFT.pde | 6 +- OpenBCI_GUI/W_Focus.pde | 6 +- OpenBCI_GUI/W_GanglionImpedance.pde | 6 +- OpenBCI_GUI/W_HeadPlot.pde | 7 +- OpenBCI_GUI/W_Marker.pde | 6 +- OpenBCI_GUI/W_PacketLoss.pde | 5 +- OpenBCI_GUI/W_Playback.pde | 5 +- OpenBCI_GUI/W_PulseSensor.pde | 6 +- OpenBCI_GUI/W_Spectrogram.pde | 6 +- OpenBCI_GUI/W_Template.pde | 124 +++++++++++++++++---------- OpenBCI_GUI/W_TimeSeries.pde | 7 +- OpenBCI_GUI/Widget.pde | 10 +-- OpenBCI_GUI/WidgetManager.pde | 125 ++++++++++++++-------------- OpenBCI_GUI/WidgetSettings.pde | 14 ++-- 21 files changed, 207 insertions(+), 164 deletions(-) diff --git a/OpenBCI_GUI/W_Accelerometer.pde b/OpenBCI_GUI/W_Accelerometer.pde index 470cdef4c..371eadce5 100644 --- a/OpenBCI_GUI/W_Accelerometer.pde +++ b/OpenBCI_GUI/W_Accelerometer.pde @@ -44,8 +44,9 @@ class W_Accelerometer extends Widget { private AccelerometerCapableBoard accelBoard; - W_Accelerometer(String widgetName) { - super(widgetName); + W_Accelerometer() { + super(); + widgetTitle = "Accelerometer"; accelBoard = (AccelerometerCapableBoard)currentBoard; diff --git a/OpenBCI_GUI/W_AnalogRead.pde b/OpenBCI_GUI/W_AnalogRead.pde index 9680cf931..00d00fc7c 100644 --- a/OpenBCI_GUI/W_AnalogRead.pde +++ b/OpenBCI_GUI/W_AnalogRead.pde @@ -9,7 +9,6 @@ //////////////////////////////////////////////////////////////////////// class W_AnalogRead extends Widget { - private float arPadding; // values for actual time series chart (rectangle encompassing all analogReadBars) private float ar_x, ar_y, ar_h, ar_w; @@ -28,8 +27,9 @@ class W_AnalogRead extends Widget { private AnalogCapableBoard analogBoard; - W_AnalogRead(String _widgetName) { - super(_widgetName); + W_AnalogRead() { + super(); + widgetTitle = "AnalogRead"; analogBoard = (AnalogCapableBoard)currentBoard; diff --git a/OpenBCI_GUI/W_BandPower.pde b/OpenBCI_GUI/W_BandPower.pde index ea9a6009f..c892b7d2d 100644 --- a/OpenBCI_GUI/W_BandPower.pde +++ b/OpenBCI_GUI/W_BandPower.pde @@ -15,7 +15,6 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////// class W_BandPower extends Widget { - // indexes private final int DELTA = 0; // 1-4 Hz private final int THETA = 1; // 4-8 Hz @@ -40,8 +39,9 @@ class W_BandPower extends Widget { int[] autoCleanTimers; boolean[] previousThresholdCrossed; - W_BandPower(String _widgetName) { - super(_widgetName); + W_BandPower() { + super(); + widgetTitle = "Band Power"; autoCleanTimers = new int[currentBoard.getNumEXGChannels()]; previousThresholdCrossed = new boolean[currentBoard.getNumEXGChannels()]; diff --git a/OpenBCI_GUI/W_CytonImpedance.pde b/OpenBCI_GUI/W_CytonImpedance.pde index 477e74f04..f7626db92 100644 --- a/OpenBCI_GUI/W_CytonImpedance.pde +++ b/OpenBCI_GUI/W_CytonImpedance.pde @@ -56,8 +56,9 @@ class W_CytonImpedance extends Widget { private int thresholdTFWidth = 60; //Hard-code this value since there are deep errors with controlp5.textfield.setSize() and creating new graphics in this class - RW 12/13/2021 - W_CytonImpedance(String _widgetName){ - super(_widgetName); + W_CytonImpedance() { + super(); + widgetTitle = "Cyton Signal"; cytonBoard = (BoardCyton) currentBoard; diff --git a/OpenBCI_GUI/W_DigitalRead.pde b/OpenBCI_GUI/W_DigitalRead.pde index 11cc081d9..4e6272427 100644 --- a/OpenBCI_GUI/W_DigitalRead.pde +++ b/OpenBCI_GUI/W_DigitalRead.pde @@ -22,8 +22,9 @@ class W_DigitalRead extends Widget { private DigitalCapableBoard digitalBoard; - W_DigitalRead(String _widgetName) { - super(_widgetName); + W_DigitalRead() { + super(); + widgetTitle = "Digital Read"; digitalBoard = (DigitalCapableBoard)currentBoard; diff --git a/OpenBCI_GUI/W_EMG.pde b/OpenBCI_GUI/W_EMG.pde index 12ab81491..301c74c75 100644 --- a/OpenBCI_GUI/W_EMG.pde +++ b/OpenBCI_GUI/W_EMG.pde @@ -21,8 +21,9 @@ class W_Emg extends Widget { public ExGChannelSelect emgChannelSelect; - W_Emg (String _widgetName) { - super(_widgetName); + W_Emg () { + super(); + widgetTitle = "EMG"; cp5ElementsToCheck = new ArrayList(); diff --git a/OpenBCI_GUI/W_EMGJoystick.pde b/OpenBCI_GUI/W_EMGJoystick.pde index 3d0ed8bea..7c97a658c 100644 --- a/OpenBCI_GUI/W_EMGJoystick.pde +++ b/OpenBCI_GUI/W_EMGJoystick.pde @@ -10,7 +10,6 @@ ///////////////////////////////////////////////////////////////////////////////////////////////////////// class W_EmgJoystick extends Widget { - private ControlP5 emgCp5; private Button emgSettingsButton; private List cp5ElementsToCheck; @@ -71,8 +70,9 @@ class W_EmgJoystick extends Widget { private PImage yPositiveInputLabelImage = loadImage("EMG_Joystick/UP_100x100.png"); private PImage yNegativeInputLabelImage = loadImage("EMG_Joystick/DOWN_100x100.png"); - W_EmgJoystick(String _widgetName) { - super(_widgetName); + W_EmgJoystick() { + super(); + widgetTitle = "EMG Joystick"; emgCp5 = new ControlP5(ourApplet); emgCp5.setGraphics(ourApplet, 0,0); diff --git a/OpenBCI_GUI/W_FFT.pde b/OpenBCI_GUI/W_FFT.pde index 80f478ad7..4851c21b8 100644 --- a/OpenBCI_GUI/W_FFT.pde +++ b/OpenBCI_GUI/W_FFT.pde @@ -13,7 +13,6 @@ /////////////////////////////////////////////////// class W_Fft extends Widget { - public ExGChannelSelect fftChanSelect; private boolean prevChanSelectIsVisible = false; @@ -28,8 +27,9 @@ class W_Fft extends Widget { List cp5ElementsToCheck = new ArrayList(); - W_Fft(String _widgetName) { - super(_widgetName); + W_Fft() { + super(); + widgetTitle = "FFT Plot"; fftChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); fftChanSelect.activateAllButtons(); diff --git a/OpenBCI_GUI/W_Focus.pde b/OpenBCI_GUI/W_Focus.pde index 2d8d96956..9f42f1b5f 100644 --- a/OpenBCI_GUI/W_Focus.pde +++ b/OpenBCI_GUI/W_Focus.pde @@ -22,7 +22,6 @@ import brainflow.LogLevels; import brainflow.MLModel; class W_Focus extends Widget { - private ExGChannelSelect focusChanSelect; private boolean prevChanSelectIsVisible = false; private AuditoryNeurofeedback auditoryNeurofeedback; @@ -64,8 +63,9 @@ class W_Focus extends Widget { List cp5ElementsToCheck = new ArrayList(); - W_Focus(String _widgetName) { - super(_widgetName); + W_Focus() { + super(); + widgetTitle = "Focus"; //Add channel select dropdown to this widget focusChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); diff --git a/OpenBCI_GUI/W_GanglionImpedance.pde b/OpenBCI_GUI/W_GanglionImpedance.pde index b8193aedf..107806795 100644 --- a/OpenBCI_GUI/W_GanglionImpedance.pde +++ b/OpenBCI_GUI/W_GanglionImpedance.pde @@ -1,9 +1,11 @@ class W_GanglionImpedance extends Widget { + Button startStopCheck; int padding = 24; - W_GanglionImpedance(String _widgetName) { - super(_widgetName); + W_GanglionImpedance() { + super(); + widgetTitle = "Ganglion Signal"; createStartStopCheck("startStopCheck", "Start Impedance Check", x + padding, y + padding, 200, NAV_HEIGHT, p4, 14, colorNotPressed, OPENBCI_DARKBLUE); } diff --git a/OpenBCI_GUI/W_HeadPlot.pde b/OpenBCI_GUI/W_HeadPlot.pde index 5495d82ea..91f21a66e 100644 --- a/OpenBCI_GUI/W_HeadPlot.pde +++ b/OpenBCI_GUI/W_HeadPlot.pde @@ -10,9 +10,7 @@ import java.util.concurrent.locks.ReentrantLock; class W_HeadPlot extends Widget { - private HeadPlot headPlot; - private HeadPlotIntensity headPlotIntensity = HeadPlotIntensity.INTENSITY_1; private HeadPlotPolarity headPlotPolarity = HeadPlotPolarity.PLUS_AND_MINUS; private HeadPlotContours headPlotContours = HeadPlotContours.ON; @@ -20,8 +18,9 @@ class W_HeadPlot extends Widget { private final float DEFAULT_VERTICAL_SCALE_UV = 200f; //this defines the Y-scale on the montage plots...this is the vertical space between traces - W_HeadPlot(String _widgetName) { - super(_widgetName); + W_HeadPlot() { + super(); + widgetTitle = "Head Plot"; addDropdown("headPlotIntensityDropdown", "Intensity", headPlotIntensity.getEnumStringsAsList(), headPlotIntensity.getIndex()); addDropdown("headPlotPolarityDropdown", "Polarity", headPlotPolarity.getEnumStringsAsList(), headPlotPolarity.getIndex()); diff --git a/OpenBCI_GUI/W_Marker.pde b/OpenBCI_GUI/W_Marker.pde index af02a5507..2bf8c6b35 100644 --- a/OpenBCI_GUI/W_Marker.pde +++ b/OpenBCI_GUI/W_Marker.pde @@ -9,7 +9,6 @@ ////////////////////////////////////////////////////// class W_Marker extends Widget { - private ControlP5 localCP5; private List cp5ElementsToCheckForOverlap; @@ -45,8 +44,9 @@ class W_Marker extends Widget { private MarkerVertScale markerVertScale = MarkerVertScale.EIGHT; private MarkerWindow markerWindow = MarkerWindow.FIVE; - W_Marker(String _widgetName) { - super(_widgetName); + W_Marker() { + super(); + widgetTitle = "Marker"; //Instantiate local cp5 for this box. This allows extra control of drawing cp5 elements specifically inside this class. localCP5 = new ControlP5(ourApplet); diff --git a/OpenBCI_GUI/W_PacketLoss.pde b/OpenBCI_GUI/W_PacketLoss.pde index 2267926d4..fbcc6bea6 100644 --- a/OpenBCI_GUI/W_PacketLoss.pde +++ b/OpenBCI_GUI/W_PacketLoss.pde @@ -37,8 +37,9 @@ class W_PacketLoss extends Widget { private CalculationWindowSize tableWindowSize = CalculationWindowSize.SECONDS10; - W_PacketLoss(String _widgetName) { - super(_widgetName); + W_PacketLoss() { + super(); + widgetTitle = "Packet Loss"; dataGrid = new Grid(5/*numRows*/, 4/*numCols*/, CELL_HEIGHT); packetLossTracker = ((Board)currentBoard).getPacketLossTracker(); diff --git a/OpenBCI_GUI/W_Playback.pde b/OpenBCI_GUI/W_Playback.pde index a6e333dbb..a5feb34c2 100644 --- a/OpenBCI_GUI/W_Playback.pde +++ b/OpenBCI_GUI/W_Playback.pde @@ -21,8 +21,9 @@ class W_playback extends Widget { private boolean menuHasUpdated = false; - W_playback(String _widgetName) { - super(_widgetName); + W_playback() { + super(); + widgetTitle = "Playback History"; cp5_playback = new ControlP5(ourApplet); cp5_playback.setGraphics(ourApplet, 0,0); diff --git a/OpenBCI_GUI/W_PulseSensor.pde b/OpenBCI_GUI/W_PulseSensor.pde index 4be2472e6..f8344b07c 100644 --- a/OpenBCI_GUI/W_PulseSensor.pde +++ b/OpenBCI_GUI/W_PulseSensor.pde @@ -9,7 +9,6 @@ //////////////////////////////////////////////////// class W_PulseSensor extends Widget { - //to see all core variables/methods of the Widget class, refer to Widget.pde //put your custom variables here... private color graphStroke = #d2d2d2; @@ -62,8 +61,9 @@ class W_PulseSensor extends Widget { private AnalogCapableBoard analogBoard; - W_PulseSensor(String _widgetName) { - super(_widgetName); + W_PulseSensor() { + super(); + widgetTitle = "Pulse Sensor"; analogBoard = (AnalogCapableBoard)currentBoard; diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index 3dbb82638..77f600111 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -8,7 +8,6 @@ ////////////////////////////////////////////////////// class W_Spectrogram extends Widget { - //to see all core variables/methods of the Widget class, refer to Widget.pde public ExGChannelSelect spectChanSelectTop; public ExGChannelSelect spectChanSelectBot; @@ -48,8 +47,9 @@ class W_Spectrogram extends Widget { private SpectrogramWindowSize windowSize = SpectrogramWindowSize.ONE_MINUTE; private FFTLogLin logLin = FFTLogLin.LIN; - W_Spectrogram(String _widgetName) { - super(_widgetName); + W_Spectrogram() { + super(); + widgetTitle = "Spectrogram"; //Add channel select dropdown to this widget spectChanSelectTop = new DualExGChannelSelect(ourApplet, x, y, w, navH, true); diff --git a/OpenBCI_GUI/W_Template.pde b/OpenBCI_GUI/W_Template.pde index fa01ef737..ad5a4f592 100644 --- a/OpenBCI_GUI/W_Template.pde +++ b/OpenBCI_GUI/W_Template.pde @@ -1,31 +1,45 @@ - -//////////////////////////////////////////////////// -// -// W_Template.pde (ie "Widget Template") -// -// This is a Template Widget, intended to be used as a starting point for OpenBCI Community members that want to develop their own custom widgets! -// Good luck! If you embark on this journey, please let us know. Your contributions are valuable to everyone! -// -// Created by: Conor Russomanno, November 2016 -// -///////////////////////////////////////////////////, +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// +//// //// +//// W_Template.pde (ie "Widget Template") //// +//// //// +//// This is a Template Widget, intended to be //// +//// used as a starting point for OpenBCI //// +//// Community members that want to develop //// +//// their own custom widgets! //// +//// //// +//// Good luck! If you embark on this journey, //// +//// please let us know. Your contributions //// +//// are valuable to everyone! //// +//// //// +//// Created: Conor Russomanno, November 2016 //// +//// Refactored: Richard Waltman, April 2025 //// +//// //// +//////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////// class W_Template extends Widget { - - //to see all core variables/methods of the Widget class, refer to Widget.pde - //put your custom variables here... - ControlP5 localCP5; - Button widgetTemplateButton; - - W_Template(String _widgetName) { - super(_widgetName); + + //To see all core variables/methods of the Widget class, refer to Widget.pde + //Put your custom variables here! Make sure to declare them as private by default. + //In Java, if you need to access a variable from another class, you should create a getter/setter methods. + //Example: public int getMyVariable(){ return myVariable; } + //Example: public void setMyVariable(int myVariable){ this.myVariable = myVariable; } + private ControlP5 localCP5; + private Button widgetTemplateButton; + + W_Template() { + // Call super() first! This sets up the widget and allows you to use all the methods in the Widget class. + super(); + // Set the title of the widget. This is what will be displayed in the GUI. + widgetTitle = "Widget Template"; //This is the protocol for setting up dropdowns. - //Note that these 3 dropdowns correspond to the 3 global functions below - //You just need to make sure the "id" (the 1st String) has the same name as the corresponding function - addDropdown("Dropdown1", "Drop 1", Arrays.asList("A", "B"), 0); - addDropdown("Dropdown2", "Drop 2", Arrays.asList("C", "D", "E"), 1); - addDropdown("Dropdown3", "Drop 3", Arrays.asList("F", "G", "H", "I"), 3); + //Note that these 3 dropdowns correspond to the 3 global functions below. + //You just need to make sure the "id" (the 1st String) has the same name as the corresponding function. + addDropdown("widgetTemplateDropdown1", "Drop 1", Arrays.asList("A", "B"), 0); + addDropdown("widgetTemplateDropdown2", "Drop 2", Arrays.asList("C", "D", "E"), 1); + addDropdown("widgetTemplateDropdown3", "Drop 3", Arrays.asList("F", "G", "H", "I"), 3); //Instantiate local cp5 for this box. This allows extra control of drawing cp5 elements specifically inside this class. @@ -101,29 +115,55 @@ class W_Template extends Widget { } + public void setDropdown1(int n){ + println("Item " + (n+1) + " selected from Dropdown 1"); + if(n == 0){ + println("Item A selected from Dropdown 1"); + } else if(n == 1){ + println("Item B selected from Dropdown 1"); + } + } + + public void setDropdown2(int n) { + println("Item " + (n+1) + " selected from Dropdown 2"); + } + + public void setDropdown3(int n) { + println("Item " + (n+1) + " selected from Dropdown 3"); + } }; /** -These functions (e.g. Dropdown1()) are global! They are activated when an item from the -corresponding dropdown is selected. While it's true they could be defined in the class above -with a CallbackListener, it's not worth the trouble (and the sheer amount of duplicated code) -for this specific kind of dropdown in each widget. In some widgets, you will see that we simply -use these global methods to call a method in the widget class. This is the best pattern to follow -due to the limitations of the ControlP5 library. -**/ -void Dropdown1(int n){ - println("Item " + (n+1) + " selected from Dropdown 1"); - if(n==0){ - //do this - } else if(n==1){ - //do this instead - } + * GLOBAL DROPDOWN HANDLERS + * + * These functions (e.g. widgetTemplateDropdown1()) are global and serve as handlers + * for dropdown events. They're activated when an item from the dropdown is selected. + * + * While these could be defined within W_Template using CallbackListeners, + * that approach would require significant duplicate code across widgets. + * + * Instead, we use this simpler pattern: + * 1. Create global functions with names matching the dropdown IDs + * 2. Each function retrieves the proper widget instance from WidgetManager + * 3. Each function calls the appropriate method on that widget + * + * This pattern is used consistently across all widgets due to ControlP5 library limitations. + */ +public void widgetTemplateDropdown1(int n) { + // Get the W_Template widget instance and call its setDropdown1 method + // Casting is necessary since widgetManager.getWidget() returns a generic Widget object + // without access to W_Template-specific methods like setDropdown1() + W_Template templateWidget = (W_Template)widgetManager.getWidget("W_Template"); + templateWidget.setDropdown1(n); } -void Dropdown2(int n){ - println("Item " + (n+1) + " selected from Dropdown 2"); +public void widgetTemplateDropdown2(int n) { + // Get widget instance and call its method (with casting for type-specific access) + W_Template widget = (W_Template)widgetManager.getWidget("W_Template"); + widget.setDropdown2(n); } -void Dropdown3(int n){ - println("Item " + (n+1) + " selected from Dropdown 3"); +public void widgetTemplateDropdown3(int n){ + // Alternate, single line version of the above + ((W_Template)widgetManager.getWidget("W_Template")).setDropdown3(n); } diff --git a/OpenBCI_GUI/W_TimeSeries.pde b/OpenBCI_GUI/W_TimeSeries.pde index 2cf69ed1a..6e5a2f069 100644 --- a/OpenBCI_GUI/W_TimeSeries.pde +++ b/OpenBCI_GUI/W_TimeSeries.pde @@ -12,8 +12,6 @@ import org.apache.commons.lang3.math.NumberUtils; class W_TimeSeries extends Widget { - //to see all core variables/methods of the Widget class, refer to Widget.pde - //put your custom variables here... private int numChannelBars; private float xF, yF, wF, hF; private float ts_padding; @@ -51,8 +49,9 @@ class W_TimeSeries extends Widget { List cp5ElementsToCheck = new ArrayList(); - W_TimeSeries(String _widgetName) { - super(_widgetName); + W_TimeSeries() { + super(); + widgetTitle = "Time Series"; tscp5 = new ControlP5(ourApplet); tscp5.setGraphics(ourApplet, 0, 0); diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index 1901a6a3f..d7251562f 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -14,21 +14,20 @@ interface IndexingInterface { } class Widget { - protected String widgetName = "Widget"; //default name of the widget + protected String widgetTitle = "Widget"; //default name of the widget protected int x0, y0, w0, h0; //true x,y,w,h of container protected int x, y, w, h; //adjusted x,y,w,h of white space `blank rectangle` under the nav... private int currentContainer; //this determines where the widget is located ... based on the x/y/w/h of the parent container + protected ControlP5 cp5_widget; + private ArrayList dropdowns; protected boolean dropdownIsActive = false; private boolean previousDropdownIsActive = false; private boolean previousTopNavDropdownMenuIsOpen = false; private boolean widgetSelectorIsActive = false; - private ArrayList dropdowns; - protected ControlP5 cp5_widget; - protected String widgetTitle = "No Title Set"; //used to limit the size of the widget selector, forces a scroll bar to show and allows us to add even more widgets in the future private final float widgetDropdownScaling = .90; private boolean isWidgetActive = false; @@ -40,8 +39,7 @@ class Widget { protected int dropdownWidth = 64; private boolean initialResize = false; //used to properly resize the widgetSelector when loading default settings - Widget(String _title) { - widgetTitle = _title; + Widget() { cp5_widget = new ControlP5(ourApplet); cp5_widget.setAutoDraw(false); //this prevents the cp5 object from drawing automatically (if it is set to true it will be drawn last, on top of all other GUI stuff... not good) dropdowns = new ArrayList(); diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index c59be1aa9..075fc68d0 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -6,12 +6,11 @@ - The order in which they are added will effect the order in which they appear in the GUI and in the WidgetSelector dropdown menu of each widget - Use the WidgetTemplate.pde file as a starting point for creating new widgets (also check out W_TimeSeries.pde, W_Fft.pde, and W_HeadPlot.pde) */ - //======================================================================================== //======================================================================================== //======================================================================================== -class WidgetManager{ +class WidgetManager { //This holds all of the widgets. When creating/adding new widgets, we will add them to this ArrayList (below) private ArrayList widgets; private ArrayList widgetOptions; //List of Widget Titles, used to populate cp5 widgetSelector dropdown of all widgets @@ -29,7 +28,7 @@ class WidgetManager{ setupWidgets(); setupWidgetSelectorDropdowns(); - if((globalChannelCount == 4 && eegDataSource == DATASOURCE_GANGLION) || eegDataSource == DATASOURCE_PLAYBACKFILE) { + if ((globalChannelCount == 4 && eegDataSource == DATASOURCE_GANGLION) || eegDataSource == DATASOURCE_PLAYBACKFILE) { currentContainerLayout = 1; sessionSettings.currentLayout = 1; } else { @@ -42,81 +41,81 @@ class WidgetManager{ void setupWidgets() { - widgets.add(new W_TimeSeries("Time Series")); + widgets.add(new W_TimeSeries()); - widgets.add(new W_Fft("FFT Plot")); + widgets.add(new W_Fft()); if (currentBoard instanceof AccelerometerCapableBoard) { - widgets.add(new W_Accelerometer("Accelerometer")); + widgets.add(new W_Accelerometer()); } if (currentBoard instanceof BoardCyton) { - widgets.add(new W_CytonImpedance("Cyton Signal")); + widgets.add(new W_CytonImpedance()); } if (currentBoard instanceof DataSourcePlayback) { - widgets.add(new W_playback("Playback History")); + widgets.add(new W_playback()); } - if(globalChannelCount == 4 && currentBoard instanceof BoardGanglion){ - widgets.add(new W_GanglionImpedance("Ganglion Signal")); + if (globalChannelCount == 4 && currentBoard instanceof BoardGanglion) { + widgets.add(new W_GanglionImpedance()); } - widgets.add(new W_Focus("Focus")); + widgets.add(new W_Focus()); - widgets.add(new W_BandPower("Band Power")); + widgets.add(new W_BandPower()); - widgets.add(new W_HeadPlot("Head Plot")); + widgets.add(new W_HeadPlot()); - widgets.add(new W_Emg("EMG")); + widgets.add(new W_Emg()); - widgets.add(new W_EmgJoystick("EMG Joystick")); + widgets.add(new W_EmgJoystick()); - widgets.add(new W_Spectrogram("Spectrogram")); + widgets.add(new W_Spectrogram()); - if (currentBoard instanceof AnalogCapableBoard){ - widgets.add(new W_PulseSensor("Pulse Sensor")); + if (currentBoard instanceof AnalogCapableBoard) { + widgets.add(new W_PulseSensor()); } if (currentBoard instanceof DigitalCapableBoard) { - widgets.add(new W_DigitalRead("Digital Read")); + widgets.add(new W_DigitalRead()); } if (currentBoard instanceof AnalogCapableBoard) { - widgets.add(new W_AnalogRead("Analog Read")); + widgets.add(new W_AnalogRead()); } if (currentBoard instanceof Board) { - widgets.add(new W_PacketLoss("Packet Loss")); + widgets.add(new W_PacketLoss()); } - widgets.add(new W_Marker("Marker")); + widgets.add(new W_Marker()); //DEVELOPERS: Here is an example widget with the essentials/structure in place - widgets.add(new W_Template("Widget Template")); + widgets.add(new W_Template()); } - void setupWidgetSelectorDropdowns(){ + void setupWidgetSelectorDropdowns() { //create the widgetSelector dropdown of each widget //println("widgets.size() = " + widgets.size()); //create list of WidgetTitles.. we will use this to populate the dropdown (widget selector) of each widget - for(int i = 0; i < widgets.size(); i++){ + for (int i = 0; i < widgets.size(); i++) { widgetOptions.add(widgets.get(i).widgetTitle); } //println("widgetOptions.size() = " + widgetOptions.size()); - for(int i = 0; i activeWidgets = new ArrayList(); - for(int i = 0; i < widgets.size(); i++){ - if(widgets.get(i).getIsActive()){ + for (int i = 0; i < widgets.size(); i++) { + if (widgets.get(i).getIsActive()) { numActiveWidgets++; //increment numActiveWidgets // activeWidgets.add(i); //keep track of the active widget } } - if(numActiveWidgets > numActiveWidgetsNeeded){ //if there are more active widgets than needed + if (numActiveWidgets > numActiveWidgetsNeeded) { //if there are more active widgets than needed //shut some down int numToShutDown = numActiveWidgets - numActiveWidgetsNeeded; int counter = 0; println("Widget Manager: Powering " + numToShutDown + " widgets down, and remapping."); - for(int i = widgets.size()-1; i >= 0; i--){ - if(widgets.get(i).getIsActive() && counter < numToShutDown){ + for (int i = widgets.size()-1; i >= 0; i--) { + if (widgets.get(i).getIsActive() && counter < numToShutDown) { verbosePrint("Widget Manager: Deactivating widget [" + i + "]"); widgets.get(i).setIsActive(false); counter++; @@ -225,20 +224,20 @@ class WidgetManager{ //and map active widgets counter = 0; - for(int i = 0; i < widgets.size(); i++){ - if(widgets.get(i).getIsActive()){ + for (int i = 0; i < widgets.size(); i++) { + if (widgets.get(i).getIsActive()) { widgets.get(i).setContainer(layouts.get(_newLayout).containerInts[counter]); counter++; } } - } else if(numActiveWidgetsNeeded > numActiveWidgets){ //if there are less active widgets than needed + } else if (numActiveWidgetsNeeded > numActiveWidgets) { //if there are less active widgets than needed //power some up int numToPowerUp = numActiveWidgetsNeeded - numActiveWidgets; int counter = 0; verbosePrint("Widget Manager: Powering " + numToPowerUp + " widgets up, and remapping."); - for(int i = 0; i < widgets.size(); i++){ - if(!widgets.get(i).getIsActive() && counter < numToPowerUp){ + for (int i = 0; i < widgets.size(); i++) { + if (!widgets.get(i).getIsActive() && counter < numToPowerUp) { verbosePrint("Widget Manager: Activating widget [" + i + "]"); widgets.get(i).setIsActive(true); counter++; @@ -247,8 +246,8 @@ class WidgetManager{ //and map active widgets counter = 0; - for(int i = 0; i < widgets.size(); i++){ - if(widgets.get(i).getIsActive()){ + for (int i = 0; i < widgets.size(); i++) { + if (widgets.get(i).getIsActive()) { widgets.get(i).setContainer(layouts.get(_newLayout).containerInts[counter]); // widgets.get(i).screenResized(); // do this to make sure the container is updated counter++; @@ -259,8 +258,8 @@ class WidgetManager{ //simply remap active widgets verbosePrint("Widget Manager: Remapping widgets."); int counter = 0; - for(int i = 0; i < widgets.size(); i++){ - if(widgets.get(i).getIsActive()){ + for (int i = 0; i < widgets.size(); i++) { + if (widgets.get(i).getIsActive()) { widgets.get(i).setContainer(layouts.get(_newLayout).containerInts[counter]); counter++; } diff --git a/OpenBCI_GUI/WidgetSettings.pde b/OpenBCI_GUI/WidgetSettings.pde index df5db7a13..2348b8c52 100644 --- a/OpenBCI_GUI/WidgetSettings.pde +++ b/OpenBCI_GUI/WidgetSettings.pde @@ -2,10 +2,10 @@ class WidgetSettings { // A map to store settings with string keys and enum values protected HashMap> settings = new HashMap>(); // Widget identifier for saving/loading specific widget settings - private String widgetName; + private String titleString; - public WidgetSettings(String widgetName) { - this.widgetName = widgetName; + public WidgetSettings(String titleString) { + this.titleString = titleString; } // Store a setting with a key and enum value @@ -29,7 +29,7 @@ class WidgetSettings { try { // Create a wrapper JSON object that contains metadata and settings JSONObject jsonData = new JSONObject(); - jsonData.setString("widgetName", widgetName); + jsonData.setString("titleString", titleString); // Create settings JSON object JSONObject settingsJson = new JSONObject(); @@ -67,9 +67,9 @@ class WidgetSettings { } // Verify widget name - String loadedWidgetName = jsonData.getString("widgetName", ""); - if (!loadedWidgetName.equals(widgetName)) { - println("Warning: Widget name mismatch. Expected: " + widgetName + ", Found: " + loadedWidgetName); + String loadedTitleString = jsonData.getString("titleString", ""); + if (!loadedTitleString.equals(titleString)) { + println("Warning: Widget name mismatch. Expected: " + titleString + ", Found: " + loadedTitleString); // Continuing anyway, might be a compatible widget } From 54ef837b87723e8985fc9ed64c6c2ba1353d7b7b Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 10 Apr 2025 22:17:53 -0500 Subject: [PATCH 11/29] Refactor WidgetManager even more for readability and maintainability --- OpenBCI_GUI/WidgetManager.pde | 245 +++++++++++++++++----------------- 1 file changed, 125 insertions(+), 120 deletions(-) diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index 075fc68d0..dc8923ea5 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -35,11 +35,12 @@ class WidgetManager { currentContainerLayout = 4; //default layout ... tall container left and 2 shorter containers stacked on the right sessionSettings.currentLayout = 4; } - - setNewContainerLayout(currentContainerLayout); //sets and fills layout with widgets in order of widget index, to reorganize widget index, reorder the creation in setupWidgets() + + //Set and fill layout with widgets in order of widget index + setNewContainerLayout(currentContainerLayout); } - void setupWidgets() { + private void setupWidgets() { widgets.add(new W_TimeSeries()); @@ -95,174 +96,181 @@ class WidgetManager { widgets.add(new W_Template()); } - void setupWidgetSelectorDropdowns() { - //create the widgetSelector dropdown of each widget - //println("widgets.size() = " + widgets.size()); - //create list of WidgetTitles.. we will use this to populate the dropdown (widget selector) of each widget - for (int i = 0; i < widgets.size(); i++) { - widgetOptions.add(widgets.get(i).widgetTitle); + private void setupWidgetSelectorDropdowns() { + // Populate the dropdown options with widget titles + for (Widget widget : widgets) { + widgetOptions.add(widget.widgetTitle); } - //println("widgetOptions.size() = " + widgetOptions.size()); - for (int i = 0; i activeWidgets = new ArrayList(); - for (int i = 0; i < widgets.size(); i++) { - if (widgets.get(i).getIsActive()) { - numActiveWidgets++; //increment numActiveWidgets - // activeWidgets.add(i); //keep track of the active widget + for (Widget widget : widgets) { + if (widget.getIsActive()) { + numActiveWidgets++; } } - if (numActiveWidgets > numActiveWidgetsNeeded) { //if there are more active widgets than needed - //shut some down + if (numActiveWidgets > numActiveWidgetsNeeded) { + // Need to deactivate some widgets int numToShutDown = numActiveWidgets - numActiveWidgetsNeeded; int counter = 0; println("Widget Manager: Powering " + numToShutDown + " widgets down, and remapping."); - for (int i = widgets.size()-1; i >= 0; i--) { - if (widgets.get(i).getIsActive() && counter < numToShutDown) { + + // Deactivate widgets starting from the end + for (int i = widgets.size()-1; i >= 0 && counter < numToShutDown; i--) { + if (widgets.get(i).getIsActive()) { verbosePrint("Widget Manager: Deactivating widget [" + i + "]"); widgets.get(i).setIsActive(false); counter++; } } - //and map active widgets - counter = 0; - for (int i = 0; i < widgets.size(); i++) { - if (widgets.get(i).getIsActive()) { - widgets.get(i).setContainer(layouts.get(_newLayout).containerInts[counter]); - counter++; - } - } + // Map active widgets to containers + mapActiveWidgetsToContainers(_newLayout); - } else if (numActiveWidgetsNeeded > numActiveWidgets) { //if there are less active widgets than needed - //power some up + } else if (numActiveWidgetsNeeded > numActiveWidgets) { + // Need to activate more widgets int numToPowerUp = numActiveWidgetsNeeded - numActiveWidgets; int counter = 0; verbosePrint("Widget Manager: Powering " + numToPowerUp + " widgets up, and remapping."); - for (int i = 0; i < widgets.size(); i++) { - if (!widgets.get(i).getIsActive() && counter < numToPowerUp) { + + // Activate widgets from the beginning + for (int i = 0; i < widgets.size() && counter < numToPowerUp; i++) { + if (!widgets.get(i).getIsActive()) { verbosePrint("Widget Manager: Activating widget [" + i + "]"); widgets.get(i).setIsActive(true); counter++; } } - //and map active widgets - counter = 0; - for (int i = 0; i < widgets.size(); i++) { - if (widgets.get(i).getIsActive()) { - widgets.get(i).setContainer(layouts.get(_newLayout).containerInts[counter]); - // widgets.get(i).screenResized(); // do this to make sure the container is updated - counter++; - } - } + // Map active widgets to containers + mapActiveWidgetsToContainers(_newLayout); - } else{ //if there are the same amount - //simply remap active widgets + } else { + // Same number of active widgets as needed, just remap verbosePrint("Widget Manager: Remapping widgets."); - int counter = 0; - for (int i = 0; i < widgets.size(); i++) { - if (widgets.get(i).getIsActive()) { - widgets.get(i).setContainer(layouts.get(_newLayout).containerInts[counter]); - counter++; - } + mapActiveWidgetsToContainers(_newLayout); + } + } + + // Helper method to map active widgets to containers + private void mapActiveWidgetsToContainers(int layoutIndex) { + int counter = 0; + for (Widget widget : widgets) { + if (widget.getIsActive()) { + widget.setContainer(layouts.get(layoutIndex).containerInts[counter]); + counter++; } } } @@ -275,31 +283,28 @@ class WidgetManager { // Useful in places like TopNav which overlap widget dropdowns public void lockCp5ObjectsInAllWidgets(boolean lock) { for (int i = 0; i < widgets.size(); i++) { - for (int j = 0; j < widgets.get(i).cp5_widget.getAll().size(); j++) { - ControlP5 cp5Instance = widgets.get(i).cp5_widget; - String widgetAddress = cp5Instance.getAll().get(j).getAddress(); - cp5Instance.getController(widgetAddress).setLock(lock); + ControlP5 cp5Instance = widgets.get(i).cp5_widget; + List controllerList = cp5Instance.getAll(); + + for (int j = 0; j < controllerList.size(); j++) { + controlP5.Controller controller = (controlP5.Controller)controllerList.get(j); + controller.setLock(lock); } } } public Widget getWidget(String className) { - for (int i = 0; i < widgets.size(); i++) { - Widget widget = widgets.get(i); - // Get the class name of the widget + for (Widget widget : widgets) { String widgetClassName = widget.getClass().getSimpleName(); - // Check if it matches the requested class name if (widgetClassName.equals(className)) { return widget; } } - // Return null if no widget of the specified class is found return null; } public boolean getWidgetExists(String className) { - Widget widget = getWidget(className); - return widget != null; + return getWidget(className) != null; } public W_TimeSeries getTimeSeriesWidget() { From 5a5053dfafcfb1118d52f4b76855fbae6c7cf268 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 10 Apr 2025 22:26:01 -0500 Subject: [PATCH 12/29] Remove widgetOptions ArrayList from WidgetManager --- OpenBCI_GUI/Widget.pde | 6 +++++- OpenBCI_GUI/WidgetManager.pde | 17 +++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index d7251562f..c45865e4b 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -48,6 +48,10 @@ class Widget { mapToCurrentContainer(); } + public String getWidgetTitle() { + return widgetTitle; + } + public boolean getIsActive() { return isWidgetActive; } @@ -225,7 +229,7 @@ class Widget { int dropdownsItemsToShow = int((h0 * widgetDropdownScaling) / (navH - 4)); widgetSelectorHeight = (dropdownsItemsToShow + 1) * (navH - 4); if (widgetManager != null) { - int maxDropdownHeight = (widgetManager.widgetOptions.size() + 1) * (navH - 4); + int maxDropdownHeight = (widgetManager.getWidgetCount() + 1) * (navH - 4); if (widgetSelectorHeight > maxDropdownHeight) widgetSelectorHeight = maxDropdownHeight; } diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index dc8923ea5..edc02ea99 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -13,7 +13,6 @@ class WidgetManager { //This holds all of the widgets. When creating/adding new widgets, we will add them to this ArrayList (below) private ArrayList widgets; - private ArrayList widgetOptions; //List of Widget Titles, used to populate cp5 widgetSelector dropdown of all widgets private int currentContainerLayout; //This is the Layout structure for the main body of the GUI private ArrayList layouts = new ArrayList(); //This holds all of the different layouts ... @@ -21,7 +20,6 @@ class WidgetManager { WidgetManager() { widgets = new ArrayList(); - widgetOptions = new ArrayList(); //DO NOT re-order the functions below setupLayouts(); @@ -97,14 +95,17 @@ class WidgetManager { } private void setupWidgetSelectorDropdowns() { - // Populate the dropdown options with widget titles + // Create a temporary list of widget titles for dropdown setup + ArrayList widgetTitles = new ArrayList(); + + // Populate the titles list by calling getWidgetTitle() on each widget for (Widget widget : widgets) { - widgetOptions.add(widget.widgetTitle); + widgetTitles.add(widget.getWidgetTitle()); } - // Setup the dropdown for each widget + // Setup the dropdown for each widget using the temporary list for (Widget widget : widgets) { - widget.setupWidgetSelectorDropdown(widgetOptions); + widget.setupWidgetSelectorDropdown(widgetTitles); widget.setupNavDropdowns(); } } @@ -310,4 +311,8 @@ class WidgetManager { public W_TimeSeries getTimeSeriesWidget() { return (W_TimeSeries) getWidget("W_TimeSeries"); } + + public int getWidgetCount() { + return widgets.size(); + } }; \ No newline at end of file From adeba05b09df741e95ae73aa1e0dc45237620746 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Mon, 14 Apr 2025 17:17:07 -0500 Subject: [PATCH 13/29] Add functional WidgetWithSettings class using the new WidgetSettings class --- OpenBCI_GUI/AccelerometerEnums.pde | 22 +- OpenBCI_GUI/AnalogReadEnums.pde | 18 -- OpenBCI_GUI/BandPowerEnums.pde | 27 --- OpenBCI_GUI/CytonImpedanceEnums.pde | 30 +-- OpenBCI_GUI/EmgJoystickEnums.pde | 23 +- OpenBCI_GUI/FFTEnums.pde | 65 +----- OpenBCI_GUI/FocusEnums.pde | 38 ---- OpenBCI_GUI/HeadPlotEnums.pde | 52 +---- OpenBCI_GUI/NetworkingEnums.pde | 49 ++--- OpenBCI_GUI/NetworkingUI.pde | 5 +- OpenBCI_GUI/SpectrogramEnums.pde | 25 +-- OpenBCI_GUI/TimeSeriesEnums.pde | 27 --- OpenBCI_GUI/W_Accelerometer.pde | 4 +- OpenBCI_GUI/W_AnalogRead.pde | 8 +- OpenBCI_GUI/W_BandPower.pde | 18 +- OpenBCI_GUI/W_CytonImpedance.pde | 10 +- OpenBCI_GUI/W_EMGJoystick.pde | 4 +- OpenBCI_GUI/W_FFT.pde | 22 +- OpenBCI_GUI/W_Focus.pde | 11 +- OpenBCI_GUI/W_HeadPlot.pde | 21 +- OpenBCI_GUI/W_Marker.pde | 25 +-- OpenBCI_GUI/W_Spectrogram.pde | 13 +- OpenBCI_GUI/W_Template.pde | 131 ++++++------ OpenBCI_GUI/W_TimeSeries.pde | 10 +- OpenBCI_GUI/Widget.pde | 33 ++- OpenBCI_GUI/WidgetSettings.pde | 318 ++++++++++++++++------------ OpenBCI_GUI/WidgetTemplateEnums.pde | 92 ++++++++ OpenBCI_GUI/WidgetWithSettings.pde | 0 28 files changed, 503 insertions(+), 598 deletions(-) create mode 100644 OpenBCI_GUI/WidgetTemplateEnums.pde create mode 100644 OpenBCI_GUI/WidgetWithSettings.pde diff --git a/OpenBCI_GUI/AccelerometerEnums.pde b/OpenBCI_GUI/AccelerometerEnums.pde index 15a4a3086..72074988c 100644 --- a/OpenBCI_GUI/AccelerometerEnums.pde +++ b/OpenBCI_GUI/AccelerometerEnums.pde @@ -8,7 +8,6 @@ public enum AccelerometerVerticalScale implements IndexingInterface private int index; private int value; private String label; - private static AccelerometerVerticalScale[] values = AccelerometerVerticalScale.values(); AccelerometerVerticalScale(int _index, int _value, String _label) { this.index = _index; @@ -30,17 +29,9 @@ public enum AccelerometerVerticalScale implements IndexingInterface return index; } - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } - public int getHighestValue() { int highestValue = 0; - for (AccelerometerVerticalScale scale : values) { + for (AccelerometerVerticalScale scale : values()) { if (scale.getValue() > highestValue) { highestValue = scale.getValue(); } @@ -60,7 +51,6 @@ public enum AccelerometerHorizontalScale implements IndexingInterface private int index; private int value; private String label; - private static AccelerometerHorizontalScale[] values = AccelerometerHorizontalScale.values(); AccelerometerHorizontalScale(int _index, int _value, String _label) { this.index = _index; @@ -82,17 +72,9 @@ public enum AccelerometerHorizontalScale implements IndexingInterface return index; } - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } - public int getHighestValue() { int highestValue = 0; - for (AccelerometerHorizontalScale scale : values) { + for (AccelerometerHorizontalScale scale : values()) { if (scale.getValue() > highestValue) { highestValue = scale.getValue(); } diff --git a/OpenBCI_GUI/AnalogReadEnums.pde b/OpenBCI_GUI/AnalogReadEnums.pde index c56040543..eb943fe08 100644 --- a/OpenBCI_GUI/AnalogReadEnums.pde +++ b/OpenBCI_GUI/AnalogReadEnums.pde @@ -11,7 +11,6 @@ public enum AnalogReadVerticalScale implements IndexingInterface private int index; private int value; private String label; - private static AnalogReadVerticalScale[] values = AnalogReadVerticalScale.values(); AnalogReadVerticalScale(int _index, int _value, String _label) { this.index = _index; @@ -32,14 +31,6 @@ public enum AnalogReadVerticalScale implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } public enum AnalogReadHorizontalScale implements IndexingInterface @@ -53,7 +44,6 @@ public enum AnalogReadHorizontalScale implements IndexingInterface private int index; private int value; private String label; - private static AnalogReadHorizontalScale[] values = AnalogReadHorizontalScale.values(); AnalogReadHorizontalScale(int _index, int _value, String _label) { this.index = _index; @@ -74,12 +64,4 @@ public enum AnalogReadHorizontalScale implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } \ No newline at end of file diff --git a/OpenBCI_GUI/BandPowerEnums.pde b/OpenBCI_GUI/BandPowerEnums.pde index cf6c322f7..1f47dc6e8 100644 --- a/OpenBCI_GUI/BandPowerEnums.pde +++ b/OpenBCI_GUI/BandPowerEnums.pde @@ -6,7 +6,6 @@ public enum BPAutoClean implements IndexingInterface private int index; private String label; - private static BPAutoClean[] vals = values(); BPAutoClean(int _index, String _label) { this.index = _index; @@ -22,14 +21,6 @@ public enum BPAutoClean implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } public enum BPAutoCleanThreshold implements IndexingInterface @@ -45,7 +36,6 @@ public enum BPAutoCleanThreshold implements IndexingInterface private int index; private float value; private String label; - private static BPAutoCleanThreshold[] vals = values(); BPAutoCleanThreshold(int _index, float _value, String _label) { this.index = _index; @@ -66,14 +56,6 @@ public enum BPAutoCleanThreshold implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } public enum BPAutoCleanTimer implements IndexingInterface @@ -89,7 +71,6 @@ public enum BPAutoCleanTimer implements IndexingInterface private int index; private float value; private String label; - private static BPAutoCleanTimer[] vals = values(); BPAutoCleanTimer(int _index, float _value, String _label) { this.index = _index; @@ -110,12 +91,4 @@ public enum BPAutoCleanTimer implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } \ No newline at end of file diff --git a/OpenBCI_GUI/CytonImpedanceEnums.pde b/OpenBCI_GUI/CytonImpedanceEnums.pde index 21713bf9d..313b8d9de 100644 --- a/OpenBCI_GUI/CytonImpedanceEnums.pde +++ b/OpenBCI_GUI/CytonImpedanceEnums.pde @@ -6,7 +6,6 @@ public enum CytonSignalCheckMode implements IndexingInterface private int index; private String label; - private static CytonSignalCheckMode[] vals = values(); CytonSignalCheckMode(int _index, String _label) { this.index = _index; @@ -26,14 +25,6 @@ public enum CytonSignalCheckMode implements IndexingInterface public boolean getIsImpedanceMode() { return label.equals("Impedance"); } - - private static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } public enum CytonImpedanceLabels implements IndexingInterface @@ -45,7 +36,6 @@ public enum CytonImpedanceLabels implements IndexingInterface private int index; private String label; private boolean boolean_value; - private static CytonImpedanceLabels[] vals = values(); CytonImpedanceLabels(int _index, String _label) { this.index = _index; @@ -65,14 +55,6 @@ public enum CytonImpedanceLabels implements IndexingInterface public boolean getIsAnatomicalName() { return label.equals("Anatomical"); } - - private static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } public enum CytonImpedanceInterval implements IndexingInterface @@ -80,14 +62,12 @@ public enum CytonImpedanceInterval implements IndexingInterface FOUR (0, 4000, "4 sec"), FIVE (1, 5000, "5 sec"), SEVEN (2, 7000, "7 sec"), - TEN (3, 10000, "10 sec") - ; + TEN (3, 10000, "10 sec"); private int index; private int value; private String label; private boolean boolean_value; - private static CytonImpedanceInterval[] vals = values(); CytonImpedanceInterval(int _index, int _val, String _label) { this.index = _index; @@ -108,12 +88,4 @@ public enum CytonImpedanceInterval implements IndexingInterface public int getValue() { return value; } - - private static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } \ No newline at end of file diff --git a/OpenBCI_GUI/EmgJoystickEnums.pde b/OpenBCI_GUI/EmgJoystickEnums.pde index 409d2b197..902739a2d 100644 --- a/OpenBCI_GUI/EmgJoystickEnums.pde +++ b/OpenBCI_GUI/EmgJoystickEnums.pde @@ -12,7 +12,6 @@ public enum EmgJoystickSmoothing implements IndexingInterface private int index; private String name; private float value; - private static EmgJoystickSmoothing[] vals = values(); EmgJoystickSmoothing(int index, String name, float value) { this.index = index; @@ -20,10 +19,12 @@ public enum EmgJoystickSmoothing implements IndexingInterface this.value = value; } + @Override public int getIndex() { return index; } + @Override public String getString() { return name; } @@ -31,17 +32,9 @@ public enum EmgJoystickSmoothing implements IndexingInterface public float getValue() { return value; } - - private static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } -public class EMGJoystickInput { +public class EMGJoystickInput implements IndexingInterface{ private int index; private String name; private int value; @@ -52,10 +45,12 @@ public class EMGJoystickInput { this.value = value; } + @Override public int getIndex() { return index; } + @Override public String getString() { return name; } @@ -100,12 +95,4 @@ public class EMGJoystickInputs { } INPUTS[inputNumber] = VALUES[channel]; } - - public List getValueStringsAsList() { - List enumStrings = new ArrayList(); - for (EMGJoystickInput val : VALUES) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } \ No newline at end of file diff --git a/OpenBCI_GUI/FFTEnums.pde b/OpenBCI_GUI/FFTEnums.pde index 4fb69f2c7..b351a543d 100644 --- a/OpenBCI_GUI/FFTEnums.pde +++ b/OpenBCI_GUI/FFTEnums.pde @@ -40,10 +40,9 @@ public enum FFTSmoothingFactor implements IndexingInterface { SMOOTH_99 (6, 0.99f, "0.99"), SMOOTH_999 (7, 0.999f, "0.999"); - private final int index; + private int index; private final float value; - private final String label; - private static final FFTSmoothingFactor[] values = values(); + private String label; FFTSmoothingFactor(int index, float value, String label) { this.index = index; @@ -64,14 +63,6 @@ public enum FFTSmoothingFactor implements IndexingInterface { public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } // Used by FFT Widget and Band Power Widget @@ -79,9 +70,8 @@ public enum FFTFilteredEnum implements IndexingInterface { FILTERED (0, "Filtered"), UNFILTERED (1, "Unfilt."); - private final int index; - private final String label; - private static final FFTFilteredEnum[] values = values(); + private int index; + private String label; FFTFilteredEnum(int index, String label) { this.index = index; @@ -97,14 +87,6 @@ public enum FFTFilteredEnum implements IndexingInterface { public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } public enum FFTMaxFrequency implements IndexingInterface { @@ -117,10 +99,9 @@ public enum FFTMaxFrequency implements IndexingInterface { MAX_500 (6, 500, "500 Hz"), MAX_800 (7, 800, "800 Hz"); - private final int index; + private int index; private final int value; - private final String label; - private static final FFTMaxFrequency[] values = values(); + private String label; FFTMaxFrequency(int index, int value, String label) { this.index = index; @@ -142,14 +123,6 @@ public enum FFTMaxFrequency implements IndexingInterface { return index; } - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } - public int getHighestFrequency() { return MAX_800.getValue(); } @@ -161,10 +134,9 @@ public enum FFTVerticalScale implements IndexingInterface { SCALE_100 (2, 100, "100 uV"), SCALE_1000 (3, 1000, "1000 uV"); - private final int index; + private int index; private final int value; - private final String label; - private static final FFTVerticalScale[] values = values(); + private String label; FFTVerticalScale(int index, int value, String label) { this.index = index; @@ -185,23 +157,14 @@ public enum FFTVerticalScale implements IndexingInterface { public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } public enum FFTLogLin implements IndexingInterface { LOG (0, "Log"), LIN (1, "Linear"); - private final int index; - private final String label; - private static final FFTLogLin[] values = values(); + private int index; + private String label; FFTLogLin(int index, String label) { this.index = index; @@ -217,12 +180,4 @@ public enum FFTLogLin implements IndexingInterface { public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } \ No newline at end of file diff --git a/OpenBCI_GUI/FocusEnums.pde b/OpenBCI_GUI/FocusEnums.pde index 7fa92c97f..59ce3191c 100644 --- a/OpenBCI_GUI/FocusEnums.pde +++ b/OpenBCI_GUI/FocusEnums.pde @@ -14,7 +14,6 @@ public enum FocusXLim implements IndexingInterface private int index; private int value; private String label; - private static FocusXLim[] vals = values(); FocusXLim(int _index, int _value, String _label) { this.index = _index; @@ -35,14 +34,6 @@ public enum FocusXLim implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } public enum FocusMetric implements IndexingInterface @@ -54,7 +45,6 @@ public enum FocusMetric implements IndexingInterface private String label; private BrainFlowMetrics metric; private String idealState; - private static FocusMetric[] vals = values(); FocusMetric(int _index, String _label, BrainFlowMetrics _metric, String _idealState) { this.index = _index; @@ -80,14 +70,6 @@ public enum FocusMetric implements IndexingInterface public String getIdealStateString() { return idealState; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } public enum FocusClassifier implements IndexingInterface @@ -99,8 +81,6 @@ public enum FocusClassifier implements IndexingInterface private String label; private BrainFlowClassifiers classifier; - private static FocusClassifier[] vals = values(); - FocusClassifier(int _index, String _label, BrainFlowClassifiers _classifier) { this.index = _index; this.label = _label; @@ -120,14 +100,6 @@ public enum FocusClassifier implements IndexingInterface public BrainFlowClassifiers getClassifier() { return classifier; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } public enum FocusThreshold implements IndexingInterface @@ -142,8 +114,6 @@ public enum FocusThreshold implements IndexingInterface private float value; private String label; - private static FocusThreshold[] vals = values(); - FocusThreshold(int _index, float _value, String _label) { this.index = _index; this.value = _value; @@ -163,12 +133,4 @@ public enum FocusThreshold implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } \ No newline at end of file diff --git a/OpenBCI_GUI/HeadPlotEnums.pde b/OpenBCI_GUI/HeadPlotEnums.pde index 8e00cf0f0..34f669593 100644 --- a/OpenBCI_GUI/HeadPlotEnums.pde +++ b/OpenBCI_GUI/HeadPlotEnums.pde @@ -7,10 +7,9 @@ public enum HeadPlotIntensity implements IndexingInterface { INTENSITY_2 (4, 2.0f, "2x"), INTENSITY_4 (5, 4.0f, "4x"); - private final int index; + private int index; private final float value; - private final String label; - private static final HeadPlotIntensity[] values = values(); + private String label; HeadPlotIntensity(int index, float value, String label) { this.index = index; @@ -31,23 +30,14 @@ public enum HeadPlotIntensity implements IndexingInterface { public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } public enum HeadPlotPolarity implements IndexingInterface { PLUS_AND_MINUS (0, "+/-"), PLUS (1, "+"); - private final int index; - private final String label; - private static final HeadPlotPolarity[] values = values(); + private int index; + private String label; HeadPlotPolarity(int index, String label) { this.index = index; @@ -63,23 +53,14 @@ public enum HeadPlotPolarity implements IndexingInterface { public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } public enum HeadPlotContours implements IndexingInterface { ON (0, "ON"), OFF (1, "OFF"); - private final int index; - private final String label; - private static final HeadPlotContours[] values = values(); + private int index; + private String label; HeadPlotContours(int index, String label) { this.index = index; @@ -95,14 +76,6 @@ public enum HeadPlotContours implements IndexingInterface { public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } public enum HeadPlotSmoothing implements IndexingInterface { @@ -115,10 +88,9 @@ public enum HeadPlotSmoothing implements IndexingInterface { SMOOTH_99 (6, 0.99f, "0.99"), SMOOTH_999 (7, 0.999f, "0.999"); - private final int index; + private int index; private final float value; - private final String label; - private static final HeadPlotSmoothing[] values = values(); + private String label; HeadPlotSmoothing(int index, float value, String label) { this.index = index; @@ -139,12 +111,4 @@ public enum HeadPlotSmoothing implements IndexingInterface { public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } \ No newline at end of file diff --git a/OpenBCI_GUI/NetworkingEnums.pde b/OpenBCI_GUI/NetworkingEnums.pde index 49a2dea9f..e03b0cccb 100644 --- a/OpenBCI_GUI/NetworkingEnums.pde +++ b/OpenBCI_GUI/NetworkingEnums.pde @@ -4,21 +4,21 @@ public enum NetworkProtocol implements IndexingInterface { LSL (2, "LSL"), SERIAL (3, "Serial"); - private final int INDEX; - private final String NAME; + private int index; + private String label; private static final NetworkProtocol[] VALUES = values(); - NetworkProtocol(int index, String name) { - INDEX = index; - NAME = name; + NetworkProtocol(int index, String label) { + this.index = index; + this.label = label; } public int getIndex() { - return INDEX; + return index; } public String getString() { - return NAME; + return label; } public static NetworkProtocol getByIndex(int _index) { @@ -38,15 +38,6 @@ public enum NetworkProtocol implements IndexingInterface { } return null; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : VALUES) { - enumStrings.add(val.getString()); - } - enumStrings.remove("Serial"); // #354 - return enumStrings; - } } public enum NetworkDataType implements IndexingInterface { @@ -63,33 +54,33 @@ public enum NetworkDataType implements IndexingInterface { EMG_JOYSTICK (9, "EMGJoystick", "emgJoystick", "emg-joystick"), MARKER (10, "Marker", "marker", "marker"); - private final int INDEX; - private final String NAME; - private final String UDP_KEY; - private final String OSC_KEY; + private int index; + private String label; + private String udpKey; + private String oscKey; private static final NetworkDataType[] VALUES = values(); - NetworkDataType(int index, String name, String udpKey, String oscKey) { - INDEX = index; - NAME = name; - UDP_KEY = udpKey; - OSC_KEY = oscKey; + NetworkDataType(int index, String label, String udpKey, String oscKey) { + this.index = index; + this.label = label; + this.udpKey = udpKey; + this.oscKey = oscKey; } public int getIndex() { - return INDEX; + return index; } public String getString() { - return NAME; + return label; } public String getUDPKey() { - return UDP_KEY; + return udpKey; } public String getOSCKey() { - return OSC_KEY; + return oscKey; } public static NetworkDataType getByString(String _name) { diff --git a/OpenBCI_GUI/NetworkingUI.pde b/OpenBCI_GUI/NetworkingUI.pde index 15f43cba0..c19b0f6f8 100644 --- a/OpenBCI_GUI/NetworkingUI.pde +++ b/OpenBCI_GUI/NetworkingUI.pde @@ -490,6 +490,7 @@ class NetworkingUI extends PApplet implements Runnable { } private void createProtocolDropdown() { + List protocolList = EnumHelper.getEnumStrings(NetworkProtocol.class); protocolDropdown = nwCp5.addScrollableList("networkingProtocolDropdown") .setOpen(false) .setOutlineColor(OPENBCI_DARKBLUE) @@ -499,10 +500,10 @@ class NetworkingUI extends PApplet implements Runnable { .setColorForeground(color(125)) // border color when not selected .setColorActive(BUTTON_PRESSED) // border color when selected // .setColorCursor(color(26,26,26)) - .setSize(TEXTFIELD_WIDTH, (NetworkProtocol.getEnumStringsAsList().size() + 1) * (ITEM_HEIGHT))// + maxFreqList.size()) + .setSize(TEXTFIELD_WIDTH, (protocolList.size() + 1) * (ITEM_HEIGHT))// + maxFreqList.size()) .setBarHeight(ITEM_HEIGHT) // height of top/primary bar .setItemHeight(ITEM_HEIGHT) // height of all item/dropdown bars - .addItems(NetworkProtocol.getEnumStringsAsList()) // used to be .addItems(maxFreqList) + .addItems(protocolList) // used to be .addItems(maxFreqList) .setVisible(true); protocolDropdown.getCaptionLabel() // the caption label is the text object in the primary bar .toUpperCase(false) // DO NOT AUTOSET TO UPPERCASE!!! diff --git a/OpenBCI_GUI/SpectrogramEnums.pde b/OpenBCI_GUI/SpectrogramEnums.pde index 82fbd0901..d67cc62b2 100644 --- a/OpenBCI_GUI/SpectrogramEnums.pde +++ b/OpenBCI_GUI/SpectrogramEnums.pde @@ -6,11 +6,10 @@ public enum SpectrogramMaxFrequency implements IndexingInterface { MAX_120 (4, 120, "120 Hz", new int[]{120, 90, 60, 30, 0, 30, 60, 90, 120}), MAX_250 (5, 250, "250 Hz", new int[]{250, 188, 125, 63, 0, 63, 125, 188, 250}); - private final int index; + private int index; private final int value; - private final String label; + private String label; private final int[] axisLabels; - private static final SpectrogramMaxFrequency[] values = values(); SpectrogramMaxFrequency(int index, int value, String label, int[] axisLabels) { this.index = index; @@ -36,14 +35,6 @@ public enum SpectrogramMaxFrequency implements IndexingInterface { public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } public enum SpectrogramWindowSize implements IndexingInterface { @@ -53,9 +44,9 @@ public enum SpectrogramWindowSize implements IndexingInterface { SIX_MINUTES (3, 6f, "6 Min.", new float[]{6, 5, 4, 3, 2, 1, 0}, 200), THIRTY_MINUTES (4, 30f, "30 Min.", new float[]{30, 25, 20, 15, 10, 5, 0}, 1000); - private final int index; + private int index; private final float value; - private final String label; + private String label; private final float[] axisLabels; private final int scrollSpeed; @@ -88,12 +79,4 @@ public enum SpectrogramWindowSize implements IndexingInterface { public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList<>(); - for (IndexingInterface enumValue : values()) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } } diff --git a/OpenBCI_GUI/TimeSeriesEnums.pde b/OpenBCI_GUI/TimeSeriesEnums.pde index 04fa7f3e5..a45a63323 100644 --- a/OpenBCI_GUI/TimeSeriesEnums.pde +++ b/OpenBCI_GUI/TimeSeriesEnums.pde @@ -9,7 +9,6 @@ public enum TimeSeriesXLim implements IndexingInterface private int index; private int value; private String label; - private static TimeSeriesXLim[] vals = values(); TimeSeriesXLim(int _index, int _value, String _label) { this.index = _index; @@ -30,14 +29,6 @@ public enum TimeSeriesXLim implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } public enum TimeSeriesYLim implements IndexingInterface @@ -55,7 +46,6 @@ public enum TimeSeriesYLim implements IndexingInterface private int index; private int value; private String label; - private static TimeSeriesYLim[] vals = values(); TimeSeriesYLim(int _index, int _value, String _label) { this.index = _index; @@ -76,14 +66,6 @@ public enum TimeSeriesYLim implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } public enum TimeSeriesLabelMode implements IndexingInterface @@ -95,7 +77,6 @@ public enum TimeSeriesLabelMode implements IndexingInterface private int index; private int value; private String label; - private static TimeSeriesLabelMode[] vals = values(); TimeSeriesLabelMode(int _index, int _value, String _label) { this.index = _index; @@ -116,12 +97,4 @@ public enum TimeSeriesLabelMode implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } \ No newline at end of file diff --git a/OpenBCI_GUI/W_Accelerometer.pde b/OpenBCI_GUI/W_Accelerometer.pde index 371eadce5..23bb484ac 100644 --- a/OpenBCI_GUI/W_Accelerometer.pde +++ b/OpenBCI_GUI/W_Accelerometer.pde @@ -51,8 +51,8 @@ class W_Accelerometer extends Widget { accelBoard = (AccelerometerCapableBoard)currentBoard; //Make dropdowns - addDropdown("accelerometerVerticalScaleDropdown", "Vert Scale", verticalScale.getEnumStringsAsList(), verticalScale.getIndex()); - addDropdown("accelerometerHorizontalScaleDropdown", "Window", horizontalScale.getEnumStringsAsList(), horizontalScale.getIndex()); + addDropdown("accelerometerVerticalScaleDropdown", "Vert Scale", EnumHelper.getEnumStrings(AccelerometerVerticalScale.class), verticalScale.getIndex()); + addDropdown("accelerometerHorizontalScaleDropdown", "Window", EnumHelper.getEnumStrings(AccelerometerHorizontalScale.class), horizontalScale.getIndex()); setGraphDimensions(); polarYMaxMin = adjustYMaxMinBasedOnSource(); diff --git a/OpenBCI_GUI/W_AnalogRead.pde b/OpenBCI_GUI/W_AnalogRead.pde index 00d00fc7c..48119fda0 100644 --- a/OpenBCI_GUI/W_AnalogRead.pde +++ b/OpenBCI_GUI/W_AnalogRead.pde @@ -33,8 +33,8 @@ class W_AnalogRead extends Widget { analogBoard = (AnalogCapableBoard)currentBoard; - addDropdown("analogReadVerticalScaleDropdown", "Vert Scale", verticalScale.getEnumStringsAsList(), verticalScale.getIndex()); - addDropdown("analogReadHorizontalScaleDropdown", "Window", horizontalScale.getEnumStringsAsList(), horizontalScale.getIndex()); + addDropdown("analogReadVerticalScaleDropdown", "Vert Scale", EnumHelper.getEnumStrings(AnalogReadVerticalScale.class), verticalScale.getIndex()); + addDropdown("analogReadHorizontalScaleDropdown", "Window", EnumHelper.getEnumStrings(AnalogReadHorizontalScale.class), horizontalScale.getIndex()); plotBottomWell = 45.0; //this appears to be an arbitrary vertical space adds GPlot leaves at bottom, I derived it through trial and error arPadding = 10.0; @@ -159,14 +159,14 @@ class W_AnalogRead extends Widget { } public void setVerticalScale(int n) { - verticalScale = AnalogReadVerticalScale.values[n]; + verticalScale = verticalScale.values()[n]; for(int i = 0; i < analogReadBars.length; i++) { analogReadBars[i].adjustVertScale(verticalScale.getValue()); } } public void setHorizontalScale(int n) { - horizontalScale = AnalogReadHorizontalScale.values[n]; + horizontalScale = horizontalScale.values()[n]; for(int i = 0; i < analogReadBars.length; i++) { analogReadBars[i].adjustTimeAxis(horizontalScale.getValue()); } diff --git a/OpenBCI_GUI/W_BandPower.pde b/OpenBCI_GUI/W_BandPower.pde index c892b7d2d..ccae0521e 100644 --- a/OpenBCI_GUI/W_BandPower.pde +++ b/OpenBCI_GUI/W_BandPower.pde @@ -51,18 +51,22 @@ class W_BandPower extends Widget { bpChanSelect.activateAllButtons(); cp5ElementsToCheck.addAll(bpChanSelect.getCp5ElementsForOverlapCheck()); + + List autoCleanList = EnumHelper.getEnumStrings(BPAutoClean.class); + List autoCleanThresholdList = EnumHelper.getEnumStrings(BPAutoCleanThreshold.class); + List autoCleanTimerList = EnumHelper.getEnumStrings(BPAutoCleanTimer.class); + List smoothingFactorList = EnumHelper.getEnumStrings(FFTSmoothingFactor.class); + List filteredEnumList = EnumHelper.getEnumStrings(FFTFilteredEnum.class); //Add settings dropdowns - //Note: This is the correct way to create a dropdown using an enum -RW - addDropdown("bandPowerAutoCleanDropdown", "AutoClean", autoClean.getEnumStringsAsList(), autoClean.getIndex()); - addDropdown("bandPowerAutoCleanThresholdDropdown", "Threshold", autoCleanThreshold.getEnumStringsAsList(), autoCleanThreshold.getIndex()); - addDropdown("bandPowerAutoCleanTimerDropdown", "Timer", autoCleanTimer.getEnumStringsAsList(), autoCleanTimer.getIndex()); - //Note: This is a legacy way to create a dropdown which is sloppy and disorganized -RW + addDropdown("bandPowerAutoCleanDropdown", "AutoClean", autoCleanList, autoClean.getIndex()); + addDropdown("bandPowerAutoCleanThresholdDropdown", "Threshold", autoCleanThresholdList, autoCleanThreshold.getIndex()); + addDropdown("bandPowerAutoCleanTimerDropdown", "Timer", autoCleanTimerList, autoCleanTimer.getIndex()); //These two dropdowns also have to mirror the settings in the FFT widget FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); - addDropdown("bandPowerSmoothingDropdown", "Smooth", smoothingFactor.getEnumStringsAsList(), smoothingFactor.getIndex()); - addDropdown("bandPowerDataFilteringDropdown", "Filtered?", filteredEnum.getEnumStringsAsList(), filteredEnum.getIndex()); + addDropdown("bandPowerSmoothingDropdown", "Smooth", smoothingFactorList, smoothingFactor.getIndex()); + addDropdown("bandPowerDataFilteringDropdown", "Filtered?", filteredEnumList, filteredEnum.getIndex()); // Setup for the BandPower plot bp_plot = new GPlot(ourApplet, x, y-NAV_HEIGHT, w, h+NAV_HEIGHT); diff --git a/OpenBCI_GUI/W_CytonImpedance.pde b/OpenBCI_GUI/W_CytonImpedance.pde index f7626db92..20843268e 100644 --- a/OpenBCI_GUI/W_CytonImpedance.pde +++ b/OpenBCI_GUI/W_CytonImpedance.pde @@ -70,10 +70,14 @@ class W_CytonImpedance extends Widget { threshold_ui_cp5.setGraphics(ourApplet, 0,0); threshold_ui_cp5.setAutoDraw(false); - addDropdown("CytonImpedance_MasterCheckInterval", "Interval", masterCheckInterval.getEnumStringsAsList(), masterCheckInterval.getIndex()); + List intervalList = EnumHelper.getEnumStrings(CytonImpedanceInterval.class); + List labelList = EnumHelper.getEnumStrings(CytonImpedanceLabels.class); + List modeList = EnumHelper.getEnumStrings(CytonSignalCheckMode.class); + + addDropdown("CytonImpedance_MasterCheckInterval", "Interval", intervalList, masterCheckInterval.getIndex()); dropdownWidth = 85; //Override the widget header dropdown width to fit "impedance" mode - addDropdown("CytonImpedance_LabelMode", "Labels", labelMode.getEnumStringsAsList(), labelMode.getIndex()); - addDropdown("CytonImpedance_Mode", "Mode", signalCheckMode.getEnumStringsAsList(), signalCheckMode.getIndex()); + addDropdown("CytonImpedance_LabelMode", "Labels", labelList, labelMode.getIndex()); + addDropdown("CytonImpedance_Mode", "Mode", modeList, signalCheckMode.getIndex()); footerHeight = navH/2; diff --git a/OpenBCI_GUI/W_EMGJoystick.pde b/OpenBCI_GUI/W_EMGJoystick.pde index 7c97a658c..1641284c5 100644 --- a/OpenBCI_GUI/W_EMGJoystick.pde +++ b/OpenBCI_GUI/W_EMGJoystick.pde @@ -96,7 +96,9 @@ class W_EmgJoystick extends Widget { plotChannelLabels[i] = Integer.toString(emgJoystickInputs.getInput(i).getIndex() + 1); } - addDropdown("emgJoystickSmoothingDropdown", "Smoothing", joystickSmoothing.getEnumStringsAsList(), joystickSmoothing.getIndex()); + List joystickSmoothingList = EnumHelper.getEnumStrings(EmgJoystickSmoothing.class); + + addDropdown("emgJoystickSmoothingDropdown", "Smoothing", joystickSmoothingList, joystickSmoothing.getIndex()); createInputDropdowns(); } diff --git a/OpenBCI_GUI/W_FFT.pde b/OpenBCI_GUI/W_FFT.pde index 4851c21b8..2fefc6996 100644 --- a/OpenBCI_GUI/W_FFT.pde +++ b/OpenBCI_GUI/W_FFT.pde @@ -36,13 +36,19 @@ class W_Fft extends Widget { cp5ElementsToCheck.addAll(fftChanSelect.getCp5ElementsForOverlapCheck()); - addDropdown("fftMaxFrequencyDropdown", "Max Hz", maxFrequency.getEnumStringsAsList(), maxFrequency.getIndex()); - addDropdown("fftVerticalScaleDropdown", "Max uV", verticalScale.getEnumStringsAsList(), verticalScale.getIndex()); - addDropdown("fftLogLinDropdown", "Log/Lin", logLin.getEnumStringsAsList(), logLin.getIndex()); + List maxFrequencyList = EnumHelper.getEnumStrings(FFTMaxFrequency.class); + List verticalScaleList = EnumHelper.getEnumStrings(FFTVerticalScale.class); + List logLinList = EnumHelper.getEnumStrings(FFTLogLin.class); + List smoothingList = EnumHelper.getEnumStrings(FFTSmoothingFactor.class); + List filteredEnumList = EnumHelper.getEnumStrings(FFTFilteredEnum.class); + + addDropdown("fftMaxFrequencyDropdown", "Max Hz", maxFrequencyList, maxFrequency.getIndex()); + addDropdown("fftVerticalScaleDropdown", "Max uV", verticalScaleList, verticalScale.getIndex()); + addDropdown("fftLogLinDropdown", "Log/Lin", logLinList, logLin.getIndex()); FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); - addDropdown("fftSmoothingDropdown", "Smooth", smoothingFactor.getEnumStringsAsList(), smoothingFactor.getIndex()); - addDropdown("fftFilteringDropdown", "Filters?", filteredEnum.getEnumStringsAsList(), filteredEnum.getIndex()); + addDropdown("fftSmoothingDropdown", "Smooth", smoothingList, smoothingFactor.getIndex()); + addDropdown("fftFilteringDropdown", "Filters?", filteredEnumList, filteredEnum.getIndex()); fftGplotPoints = new GPointsArray[globalChannelCount]; initializeFFTPlot(); @@ -177,17 +183,17 @@ class W_Fft extends Widget { } public void setMaxFrequency(int n) { - maxFrequency = FFTMaxFrequency.values[n]; + maxFrequency = maxFrequency.values()[n]; fftPlot.setXLim(0.1, maxFrequency.getValue()); } public void setVerticalScale(int n) { - verticalScale = FFTVerticalScale.values[n]; + verticalScale = verticalScale.values()[n]; fftPlot.setYLim(0.1, verticalScale.getValue()); } public void setLogLin(int n) { - logLin = FFTLogLin.values[n]; + logLin = logLin.values()[n]; setPlotLogScale(); } diff --git a/OpenBCI_GUI/W_Focus.pde b/OpenBCI_GUI/W_Focus.pde index 9f42f1b5f..5f03b498c 100644 --- a/OpenBCI_GUI/W_Focus.pde +++ b/OpenBCI_GUI/W_Focus.pde @@ -84,11 +84,14 @@ class W_Focus extends Widget { // initialize graphics parameters onColorChange(); - //This is the protocol for setting up dropdowns. + List metricList = EnumHelper.getEnumStrings(FocusMetric.class); + List thresholdList = EnumHelper.getEnumStrings(FocusThreshold.class); + List xLimitList = EnumHelper.getEnumStrings(FocusXLim.class); + dropdownWidth = 60; //Override the default dropdown width for this widget - addDropdown("focusMetricDropdown", "Metric", focusMetric.getEnumStringsAsList(), focusMetric.getIndex()); - addDropdown("focusThresholdDropdown", "Threshold", focusThreshold.getEnumStringsAsList(), focusThreshold.getIndex()); - addDropdown("focusWindowDropdown", "Window", xLimit.getEnumStringsAsList(), xLimit.getIndex()); + addDropdown("focusMetricDropdown", "Metric", metricList, focusMetric.getIndex()); + addDropdown("focusThresholdDropdown", "Threshold", thresholdList, focusThreshold.getIndex()); + addDropdown("focusWindowDropdown", "Window", xLimitList, xLimit.getIndex()); //Create data table dataGrid = new Grid(NUM_TABLE_ROWS, NUM_TABLE_COLUMNS, cellHeight); diff --git a/OpenBCI_GUI/W_HeadPlot.pde b/OpenBCI_GUI/W_HeadPlot.pde index 91f21a66e..d31f0db9e 100644 --- a/OpenBCI_GUI/W_HeadPlot.pde +++ b/OpenBCI_GUI/W_HeadPlot.pde @@ -22,10 +22,15 @@ class W_HeadPlot extends Widget { super(); widgetTitle = "Head Plot"; - addDropdown("headPlotIntensityDropdown", "Intensity", headPlotIntensity.getEnumStringsAsList(), headPlotIntensity.getIndex()); - addDropdown("headPlotPolarityDropdown", "Polarity", headPlotPolarity.getEnumStringsAsList(), headPlotPolarity.getIndex()); - addDropdown("headPlotContoursDropdown", "Contours", headPlotContours.getEnumStringsAsList(), headPlotContours.getIndex()); - addDropdown("headPlotSmoothingDropdown", "Smooth", headPlotSmoothing.getEnumStringsAsList(), headPlotSmoothing.getIndex()); + List headPlotIntensityList = EnumHelper.getEnumStrings(HeadPlotIntensity.class); + List headPlotPolarityList = EnumHelper.getEnumStrings(HeadPlotPolarity.class); + List headPlotContoursList = EnumHelper.getEnumStrings(HeadPlotContours.class); + List headPlotSmoothingList = EnumHelper.getEnumStrings(HeadPlotSmoothing.class); + + addDropdown("headPlotIntensityDropdown", "Intensity", headPlotIntensityList, headPlotIntensity.getIndex()); + addDropdown("headPlotPolarityDropdown", "Polarity", headPlotPolarityList, headPlotPolarity.getIndex()); + addDropdown("headPlotContoursDropdown", "Contours", headPlotContoursList, headPlotContours.getIndex()); + addDropdown("headPlotSmoothingDropdown", "Smooth", headPlotSmoothingList, headPlotSmoothing.getIndex()); updateHeadPlot(); } @@ -74,23 +79,23 @@ class W_HeadPlot extends Widget { } public void setIntensity(int n) { - headPlotIntensity = HeadPlotIntensity.values[n]; + headPlotIntensity = headPlotIntensity.values()[n]; float maxIntensityUv = DEFAULT_VERTICAL_SCALE_UV * headPlotIntensity.getValue(); headPlot.setMaxIntensity_uV(maxIntensityUv); } public void setPolarity(int n) { - headPlotPolarity = HeadPlotPolarity.values[n]; + headPlotPolarity = headPlotPolarity.values()[n]; headPlot.use_polarity = headPlotPolarity == HeadPlotPolarity.PLUS_AND_MINUS; } public void setContours(int n) { - headPlotContours = HeadPlotContours.values[n]; + headPlotContours = headPlotContours.values()[n]; headPlot.drawHeadAsContours = headPlotContours == HeadPlotContours.ON; } public void setSmoothing(int n) { - headPlotSmoothing = HeadPlotSmoothing.values[n]; + headPlotSmoothing = headPlotSmoothing.values()[n]; headPlot.smoothingFactor = headPlotSmoothing.getValue(); } }; diff --git a/OpenBCI_GUI/W_Marker.pde b/OpenBCI_GUI/W_Marker.pde index 2bf8c6b35..58a849630 100644 --- a/OpenBCI_GUI/W_Marker.pde +++ b/OpenBCI_GUI/W_Marker.pde @@ -55,8 +55,11 @@ class W_Marker extends Widget { createMarkerButtons(); - addDropdown("markerVertScaleDropdown", "Vert Scale", markerVertScale.getEnumStringsAsList(), markerVertScale.getIndex()); - addDropdown("markerWindowDropdown", "Window", markerWindow.getEnumStringsAsList(), markerWindow.getIndex()); + List verticalScaleList = EnumHelper.getEnumStrings(MarkerVertScale.class); + List windowList = EnumHelper.getEnumStrings(MarkerWindow.class); + + addDropdown("markerVertScaleDropdown", "Vert Scale", verticalScaleList, markerVertScale.getIndex()); + addDropdown("markerWindowDropdown", "Window", windowList, markerWindow.getIndex()); updateGraphDims(); markerBar = new MarkerBar(ourApplet, MAX_NUMBER_OF_MARKER_BUTTONS, markerWindow.getValue(), markerVertScale.getValue(), graphX, graphY, graphW, graphH); @@ -519,7 +522,6 @@ public enum MarkerWindow implements IndexingInterface private int index; private int value; private String label; - private static MarkerWindow[] vals = values(); MarkerWindow(int _index, int _value, String _label) { this.index = _index; @@ -540,14 +542,6 @@ public enum MarkerWindow implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } public enum MarkerVertScale implements IndexingInterface @@ -562,7 +556,6 @@ public enum MarkerVertScale implements IndexingInterface private int index; private int value; private String label; - private static MarkerVertScale[] vals = values(); MarkerVertScale(int _index, int _value, String _label) { this.index = _index; @@ -583,14 +576,6 @@ public enum MarkerVertScale implements IndexingInterface public int getIndex() { return index; } - - public static List getEnumStringsAsList() { - List enumStrings = new ArrayList(); - for (IndexingInterface val : vals) { - enumStrings.add(val.getString()); - } - return enumStrings; - } } //The following global functions are used by the Marker widget dropdowns. This method is the least amount of code. diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index 77f600111..491c8148e 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -70,12 +70,13 @@ class W_Spectrogram extends Widget { //Fetch/calculate the time strings for the horizontal axis ticks horizontalAxisLabelStrings = fetchTimeStrings(); - //This is the protocol for setting up dropdowns. - //Note that these 3 dropdowns correspond to the 3 global functions below - //You just need to make sure the "id" (the 1st String) has the same name as the corresponding function - addDropdown("spectrogramMaxFrequencyDropdown", "Max Hz", maxFrequency.getEnumStringsAsList(), maxFrequency.getIndex()); - addDropdown("spectrogramSampleRateDropdown", "Window", windowSize.getEnumStringsAsList(), windowSize.getIndex()); - addDropdown("spectrogramLogLinDropdown", "Log/Lin", logLin.getEnumStringsAsList(), logLin.getIndex()); + List maxFrequencyList = EnumHelper.getEnumStrings(SpectrogramMaxFrequency.class); + List windowSizeList = EnumHelper.getEnumStrings(SpectrogramWindowSize.class); + List logLinList = EnumHelper.getEnumStrings(FFTLogLin.class); + + addDropdown("spectrogramMaxFrequencyDropdown", "Max Hz", maxFrequencyList, maxFrequency.getIndex()); + addDropdown("spectrogramWindowDropdown", "Window", windowSizeList, windowSize.getIndex()); + addDropdown("spectrogramLogLinDropdown", "Log/Lin", logLinList, logLin.getIndex()); //Resize the height of the data image using default dataImageH = maxFrequency.getAxisLabels()[0] * 2; diff --git a/OpenBCI_GUI/W_Template.pde b/OpenBCI_GUI/W_Template.pde index ad5a4f592..cb42436e9 100644 --- a/OpenBCI_GUI/W_Template.pde +++ b/OpenBCI_GUI/W_Template.pde @@ -18,13 +18,13 @@ //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// -class W_Template extends Widget { +class W_Template extends WidgetWithSettings { - //To see all core variables/methods of the Widget class, refer to Widget.pde - //Put your custom variables here! Make sure to declare them as private by default. - //In Java, if you need to access a variable from another class, you should create a getter/setter methods. - //Example: public int getMyVariable(){ return myVariable; } - //Example: public void setMyVariable(int myVariable){ this.myVariable = myVariable; } + // To see all core variables/methods of the Widget class, refer to Widget.pde + // Put your custom variables here! Make sure to declare them as private by default. + // In Java, if you need to access a variable from another class, you should create a getter/setter methods. + // Example: public int getMyVariable(){ return myVariable; } + // Example: public void setMyVariable(int myVariable){ this.myVariable = myVariable; } private ControlP5 localCP5; private Button widgetTemplateButton; @@ -33,16 +33,8 @@ class W_Template extends Widget { super(); // Set the title of the widget. This is what will be displayed in the GUI. widgetTitle = "Widget Template"; - - //This is the protocol for setting up dropdowns. - //Note that these 3 dropdowns correspond to the 3 global functions below. - //You just need to make sure the "id" (the 1st String) has the same name as the corresponding function. - addDropdown("widgetTemplateDropdown1", "Drop 1", Arrays.asList("A", "B"), 0); - addDropdown("widgetTemplateDropdown2", "Drop 2", Arrays.asList("C", "D", "E"), 1); - addDropdown("widgetTemplateDropdown3", "Drop 3", Arrays.asList("F", "G", "H", "I"), 3); - - //Instantiate local cp5 for this box. This allows extra control of drawing cp5 elements specifically inside this class. + // Instantiate local cp5 for this box. This allows extra control of drawing cp5 elements specifically inside this class. localCP5 = new ControlP5(ourApplet); localCP5.setGraphics(ourApplet, 0,0); localCP5.setAutoDraw(false); @@ -51,56 +43,91 @@ class W_Template extends Widget { } + @Override + protected void initWidgetSettings() { + super.initWidgetSettings(); + // Widget Settings are used to store the state of the widget. + // This is where you can set the default values for your dropdowns and other settings. + widgetSettings.set(TemplateDropdown1.class, TemplateDropdown1.ITEM_A); + widgetSettings.set(TemplateDropdown2.class, TemplateDropdown2.ITEM_C); + widgetSettings.set(TemplateDropdown3.class, TemplateDropdown3.ITEM_F); + widgetSettings.saveDefaults(); + + // Get the list of Strings for the dropdowns. These are the options that will be displayed in the dropdowns. + List dropdown1List = EnumHelper.getEnumStrings(TemplateDropdown1.class); + List dropdown2List = EnumHelper.getEnumStrings(TemplateDropdown2.class); + List dropdown3List = EnumHelper.getEnumStrings(TemplateDropdown3.class); + + // Get the current settings for the dropdowns. This is where you can retrieve the values that were just set. + int dropdown1Index = widgetSettings.get(TemplateDropdown1.class).getIndex(); + int dropdown2Index = widgetSettings.get(TemplateDropdown2.class).getIndex(); + int dropdown3Index = widgetSettings.get(TemplateDropdown3.class).getIndex(); + + // This is the protocol for setting up dropdowns. + // Note that these 3 dropdowns correspond to the 3 global functions below. + // You just need to make sure the "id" (the 1st String) has the same name as the corresponding function. + addDropdown("widgetTemplateDropdown1", "Drop 1", dropdown1List, dropdown1Index); + addDropdown("widgetTemplateDropdown2", "Drop 2", dropdown2List, dropdown2Index); + addDropdown("widgetTemplateDropdown3", "Drop 3", dropdown3List, dropdown3Index); + } + + @Override + protected void applySettings() { + //FIX ME + println("!!!!!!!!!!!!!!!!!!!!!!!!!Applying settings for " + widgetTitle); + //widgetSettings.applySettings(); + } + public void update(){ super.update(); - //put your code here... + // put your code here... } public void draw(){ super.draw(); - //remember to refer to x,y,w,h which are the positioning variables of the Widget class + // remember to refer to x,y,w,h which are the positioning variables of the Widget class - //This draws all cp5 objects in the local instance + // This draws all cp5 objects in the local instance localCP5.draw(); } public void screenResized(){ super.screenResized(); - //Very important to allow users to interact with objects after app resize + // Very important to allow users to interact with objects after app resize localCP5.setGraphics(ourApplet, 0, 0); - //We need to set the position of our Cp5 object after the screen is resized + // We need to set the position of our Cp5 object after the screen is resized widgetTemplateButton.setPosition(x + w/2 - widgetTemplateButton.getWidth()/2, y + h/2 - widgetTemplateButton.getHeight()/2); } public void mousePressed(){ super.mousePressed(); - //Since GUI v5, these methods should not really be used. - //Instead, use ControlP5 objects and callbacks. - //Example: createWidgetTemplateButton() found below + // Since GUI v5, these methods should not really be used. + // Instead, use ControlP5 objects and callbacks. + // Example: createWidgetTemplateButton() found below } public void mouseReleased(){ super.mouseReleased(); - //Since GUI v5, these methods should not really be used. + // Since GUI v5, these methods should not really be used. } - //When creating new UI objects, follow this rough pattern. - //Using custom methods like this allows us to condense the code required to create new objects. - //You can find more detailed examples in the Control Panel, where there are many UI objects with varying functionality. + // When creating new UI objects, follow this rough pattern. + // Using custom methods like this allows us to condense the code required to create new objects. + // You can find more detailed examples in the Control Panel, where there are many UI objects with varying functionality. private void createWidgetTemplateButton() { - //This is a generalized createButton method that allows us to save code by using a few patterns and method overloading + // This is a generalized createButton method that allows us to save code by using a few patterns and method overloading widgetTemplateButton = createButton(localCP5, "widgetTemplateButton", "Design Your Own Widget!", x + w/2, y + h/2, 200, NAV_HEIGHT, p4, 14, colorNotPressed, OPENBCI_DARKBLUE); - //Set the border color explicitely + // Set the border color explicitely widgetTemplateButton.setBorderColor(OBJECT_BORDER_GREY); - //For this button, only call the callback listener on mouse release + // For this button, only call the callback listener on mouse release widgetTemplateButton.onRelease(new CallbackListener() { public void controlEvent(CallbackEvent theEvent) { - //If using a TopNav object, ignore interaction with widget object (ex. widgetTemplateButton) + // If using a TopNav object, ignore interaction with widget object (ex. widgetTemplateButton) if (!topNav.configSelector.isVisible && !topNav.layoutSelector.isVisible) { openURLInBrowser("https://docs.openbci.com/Software/OpenBCISoftware/GUIWidgets/#custom-widget"); } @@ -109,13 +136,14 @@ class W_Template extends Widget { widgetTemplateButton.setDescription("Here is the description for this UI object. It will fade in as help text when hovering over the object."); } - //add custom functions here + // add custom functions here private void customFunction(){ - //this is a fake function... replace it with something relevant to this widget + // this is a fake function... replace it with something relevant to this widget } public void setDropdown1(int n){ + widgetSettings.setByIndex(TemplateDropdown1.class, n); println("Item " + (n+1) + " selected from Dropdown 1"); if(n == 0){ println("Item A selected from Dropdown 1"); @@ -125,45 +153,12 @@ class W_Template extends Widget { } public void setDropdown2(int n) { + widgetSettings.setByIndex(TemplateDropdown2.class, n); println("Item " + (n+1) + " selected from Dropdown 2"); } public void setDropdown3(int n) { + widgetSettings.setByIndex(TemplateDropdown3.class, n); println("Item " + (n+1) + " selected from Dropdown 3"); } }; - -/** - * GLOBAL DROPDOWN HANDLERS - * - * These functions (e.g. widgetTemplateDropdown1()) are global and serve as handlers - * for dropdown events. They're activated when an item from the dropdown is selected. - * - * While these could be defined within W_Template using CallbackListeners, - * that approach would require significant duplicate code across widgets. - * - * Instead, we use this simpler pattern: - * 1. Create global functions with names matching the dropdown IDs - * 2. Each function retrieves the proper widget instance from WidgetManager - * 3. Each function calls the appropriate method on that widget - * - * This pattern is used consistently across all widgets due to ControlP5 library limitations. - */ -public void widgetTemplateDropdown1(int n) { - // Get the W_Template widget instance and call its setDropdown1 method - // Casting is necessary since widgetManager.getWidget() returns a generic Widget object - // without access to W_Template-specific methods like setDropdown1() - W_Template templateWidget = (W_Template)widgetManager.getWidget("W_Template"); - templateWidget.setDropdown1(n); -} - -public void widgetTemplateDropdown2(int n) { - // Get widget instance and call its method (with casting for type-specific access) - W_Template widget = (W_Template)widgetManager.getWidget("W_Template"); - widget.setDropdown2(n); -} - -public void widgetTemplateDropdown3(int n){ - // Alternate, single line version of the above - ((W_Template)widgetManager.getWidget("W_Template")).setDropdown3(n); -} diff --git a/OpenBCI_GUI/W_TimeSeries.pde b/OpenBCI_GUI/W_TimeSeries.pde index 6e5a2f069..d03658b0b 100644 --- a/OpenBCI_GUI/W_TimeSeries.pde +++ b/OpenBCI_GUI/W_TimeSeries.pde @@ -76,10 +76,14 @@ class W_TimeSeries extends Widget { ts_h = hF - playbackWidgetHeight - plotBottomWell - (ts_padding*2); numChannelBars = globalChannelCount; //set number of channel bars = to current globalChannelCount of system (4, 8, or 16) + List verticalScaleList = EnumHelper.getEnumStrings(TimeSeriesYLim.class); + List horizontalScaleList = EnumHelper.getEnumStrings(TimeSeriesXLim.class); + List labelModeList = EnumHelper.getEnumStrings(TimeSeriesLabelMode.class); + //This is a newer protocol for setting up dropdowns. - addDropdown("timeSeriesVerticalScaleDropdown", "Vert Scale", yLimit.getEnumStringsAsList(), yLimit.getIndex()); - addDropdown("timeSeriesHorizontalScaleDropdown", "Window", xLimit.getEnumStringsAsList(), xLimit.getIndex()); - addDropdown("timeSeriesLabelModeDropdown", "Labels", labelMode.getEnumStringsAsList(), labelMode.getIndex()); + addDropdown("timeSeriesVerticalScaleDropdown", "Vert Scale", verticalScaleList, yLimit.getIndex()); + addDropdown("timeSeriesHorizontalScaleDropdown", "Window", horizontalScaleList, xLimit.getIndex()); + addDropdown("timeSeriesLabelModeDropdown", "Labels", labelModeList, labelMode.getIndex()); //Instantiate scrollbar if using playback mode and scrollbar feature in use if((currentBoard instanceof FileBoard) && hasScrollbar) { diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index c45865e4b..925225c90 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -7,12 +7,6 @@ // /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//Used for Widget Dropdown Enums -interface IndexingInterface { - public int getIndex(); - public String getString(); -} - class Widget { protected String widgetTitle = "Widget"; //default name of the widget @@ -313,6 +307,33 @@ class Widget { } }; //end of base Widget class +abstract class WidgetWithSettings extends Widget { + // This class is used to add settings to a widget. It is a subclass of the Widget class. + // It is used to add settings to the widget and to save and load the settings from a file. + + protected WidgetSettings widgetSettings; + + WidgetWithSettings() { + super(); + initWidgetSettings(); + } + + public void setWidgetSettings(WidgetSettings _widgetSettings) { + //FIX ME - DO I NEED THIS? + widgetSettings = _widgetSettings; + } + + public WidgetSettings getWidgetSettings() { + // FIX ME - DO I NEED THIS? + return widgetSettings; + } + + protected void initWidgetSettings() { + widgetSettings = new WidgetSettings(getWidgetTitle()); + } + protected abstract void applySettings(); +} + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // NavBarDropdown is a single dropdown item in any instance of a Widget diff --git a/OpenBCI_GUI/WidgetSettings.pde b/OpenBCI_GUI/WidgetSettings.pde index 2348b8c52..b284b11ac 100644 --- a/OpenBCI_GUI/WidgetSettings.pde +++ b/OpenBCI_GUI/WidgetSettings.pde @@ -1,166 +1,224 @@ -class WidgetSettings { - // A map to store settings with string keys and enum values - protected HashMap> settings = new HashMap>(); - // Widget identifier for saving/loading specific widget settings - private String titleString; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; - public WidgetSettings(String titleString) { - this.titleString = titleString; +//Used for Widget Dropdown Enums +interface IndexingInterface { + public int getIndex(); + public String getString(); +} + +/** + * Helper class for working with IndexingInterface enums + */ +public static class EnumHelper { + /** + * Generic method to get enum strings as a list + */ + public static List getListAsStrings(T[] values) { + List enumStrings = new ArrayList<>(); + for (T enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } + + /** + * Get list of strings for an enum class that implements IndexingInterface + */ + public static & IndexingInterface> List getEnumStrings(Class enumClass) { + return getListAsStrings(enumClass.getEnumConstants()); + } +} + +/** + * Simple storage for widget settings that converts to/from JSON + */ +class WidgetSettings { + private String widgetName; + private HashMap> settings; + private HashMap> defaults; + + public WidgetSettings(String widgetName) { + this.widgetName = widgetName; + this.settings = new HashMap>(); + this.defaults = new HashMap>(); } - // Store a setting with a key and enum value - public > void setSetting(String key, T value) { - settings.put(key, value); + /** + * Store a setting using enum class as key + */ + public > void set(Class enumClass, T value) { + settings.put(enumClass.getName(), value); } - // Retrieve a setting by key, with optional default value - public > T getSetting(String key, T defaultValue) { - if (settings.containsKey(key) && settings.get(key).getClass() == defaultValue.getClass()) { - return (T) settings.get(key); + /** + * Store a setting using the enum class and index + * Useful for setting values from UI components like dropdowns + * + * @param enumClass The enum class to look up values + * @param index The index of the enum constant to set + * @return true if successful, false if the index is out of bounds + */ + public > boolean setByIndex(Class enumClass, int index) { + T[] enumConstants = enumClass.getEnumConstants(); + + // Check if index is valid + if (index >= 0 && index < enumConstants.length) { + // Get the enum value at the specified index + T value = enumConstants[index]; + // Set it using the regular set method + set(enumClass, value); + return true; } - return defaultValue; + + // Index was out of bounds + println("Warning: Invalid index " + index + " for enum " + enumClass.getName()); + return false; } /** - * Converts settings to a JSON string - * @return JSON string representation of the settings + * Get a setting using enum class as key */ - public String getJSON() { - try { - // Create a wrapper JSON object that contains metadata and settings - JSONObject jsonData = new JSONObject(); - jsonData.setString("titleString", titleString); - - // Create settings JSON object - JSONObject settingsJson = new JSONObject(); - - // Add each setting to the JSON object with its class and value for proper deserialization - for (Map.Entry> entry : settings.entrySet()) { - JSONObject enumValue = new JSONObject(); - Enum value = entry.getValue(); - enumValue.setString("enumClass", value.getClass().getName()); - enumValue.setString("enumValue", value.name()); - settingsJson.setJSONObject(entry.getKey(), enumValue); + public > T get(Class enumClass, T defaultValue) { + String key = enumClass.getName(); + if (settings.containsKey(key)) { + Object value = settings.get(key); + if (value != null && enumClass.isInstance(value)) { + return enumClass.cast(value); } - - jsonData.setJSONObject("settings", settingsJson); - return jsonData.toString(); - } catch (Exception e) { - println("Error converting settings to JSON: " + e.getMessage()); - e.printStackTrace(); - return "{}"; } + return defaultValue; } /** - * Loads settings from a JSON string - * @param jsonString JSON string to load settings from - * @return true if successful, false otherwise + * Get a setting using enum class as key (returns null if not found) */ - public boolean loadJSON(String jsonString) { - try { - // Parse the JSON string - JSONObject jsonData = parseJSONObject(jsonString); - if (jsonData == null) { - println("Invalid JSON string"); - return false; + public > T get(Class enumClass) { + String key = enumClass.getName(); + if (settings.containsKey(key)) { + Object value = settings.get(key); + if (value != null && enumClass.isInstance(value)) { + return enumClass.cast(value); } + } + return null; + } - // Verify widget name - String loadedTitleString = jsonData.getString("titleString", ""); - if (!loadedTitleString.equals(titleString)) { - println("Warning: Widget name mismatch. Expected: " + titleString + ", Found: " + loadedTitleString); - // Continuing anyway, might be a compatible widget - } - - // Clear existing settings - settings.clear(); + /** + * Save current settings as defaults + */ + public void saveDefaults() { + defaults = new HashMap>(settings); + } + + /** + * Restore to default settings + */ + public void restoreDefaults() { + settings = new HashMap>(defaults); + } + + /** + * Convert settings to JSON string + */ + public String toJSON() { + JSONObject json = new JSONObject(); + json.setString("widget", widgetName); + + JSONArray items = new JSONArray(); + int i = 0; + + for (String key : settings.keySet()) { + Enum value = settings.get(key); + JSONObject item = new JSONObject(); + item.setString("class", key); + item.setString("value", value.name()); + items.setJSONObject(i++, item); + } + + json.setJSONArray("settings", items); + return json.toString(); + } + + /** + * Load settings from JSON string + */ + public boolean fromJSON(String jsonString) { + try { + JSONObject json = parseJSONObject(jsonString); + if (json == null) return false; - // Load settings - JSONObject settingsJson = jsonData.getJSONObject("settings"); - if (settingsJson == null) { - println("No settings found in JSON"); - return false; + String loadedWidget = json.getString("widget", ""); + if (!loadedWidget.equals(widgetName)) { + println("Warning: Widget mismatch. Expected: " + widgetName + ", Found: " + loadedWidget); } - // Loop through each setting in the JSON - for (Object key : settingsJson.keys()) { - String settingKey = (String)key; - JSONObject enumData = settingsJson.getJSONObject(settingKey); - - String enumClassName = enumData.getString("enumClass", ""); - String enumValueName = enumData.getString("enumValue", ""); - - // Skip if missing required data - if (enumClassName.isEmpty() || enumValueName.isEmpty()) { - continue; - } - - try { - // Load the enum class - Class enumClass = Class.forName(enumClassName); - if (!enumClass.isEnum()) { - println("Class " + enumClassName + " is not an enum"); - continue; - } + JSONArray items = json.getJSONArray("settings"); + if (items != null) { + for (int i = 0; i < items.size(); i++) { + JSONObject item = items.getJSONObject(i); + String className = item.getString("class"); + String valueName = item.getString("value"); - // Get the enum value - @SuppressWarnings("unchecked") - Enum enumValue = Enum.valueOf((Class)enumClass, enumValueName); - settings.put(settingKey, enumValue); - } catch (ClassNotFoundException e) { - println("Enum class not found: " + enumClassName); - } catch (IllegalArgumentException e) { - println("Enum value not found: " + enumValueName); - } catch (Exception e) { - println("Error loading enum: " + e.getMessage()); + try { + Class enumClass = Class.forName(className); + if (enumClass.isEnum()) { + @SuppressWarnings("unchecked") + Enum enumValue = Enum.valueOf((Class)enumClass, valueName); + settings.put(className, enumValue); + } + } catch (Exception e) { + println("Error loading setting: " + e.getMessage()); + } } + return true; } - - return true; } catch (Exception e) { - println("Error loading settings from JSON: " + e.getMessage()); - e.printStackTrace(); - return false; + println("Error parsing JSON: " + e.getMessage()); } - } - - // Apply settings to UI components - public void applySettingsToCp5(ControlP5 cp5) { - // This is a default implementation - // Widget-specific classes should override this method + return false; } } -// Example extension of WidgetSettings for a specific widget +/** + * Example usage + */ + /* class ExampleWidgetSettings extends WidgetSettings { + enum Mode { NORMAL, EXPERT, DEBUG } + enum Filter { NONE, LOW_PASS, HIGH_PASS, BAND_PASS } + public ExampleWidgetSettings() { - super("ExampleWidget"); + super("Example"); + + // Set defaults + set(Mode.class, Mode.NORMAL); + set(Filter.class, Filter.NONE); + saveDefaults(); } - // Override to implement specific UI binding - @Override - public void applySettingsToCp5(ControlP5 cp5) { - // Example: Apply dropdown settings - for (String key : settings.keySet()) { - Enum value = settings.get(key); - - if (value instanceof IndexingInterface) { - IndexingInterface enumValue = (IndexingInterface)value; - ScrollableList dropdown = (ScrollableList)cp5.getController(key); - if (dropdown != null) { - dropdown.setValue(enumValue.getIndex()); - } - } - - // Handle other control types as needed - // Toggle, RadioButton, Slider, etc. - } + public void applyToUI() { + Mode mode = get(Mode.class, Mode.NORMAL); + Filter filter = get(Filter.class, Filter.NONE); + + // Apply to UI controls + println("Mode: " + mode + ", Filter: " + filter); } - // Additional methods specific to this widget - public void setupDefaultSettings() { - // Set default values for this widget - // Example: setSetting("mode", SomeEnum.DEFAULT_MODE); + public void exampleUsage() { + // Set some values + set(Mode.class, Mode.EXPERT); + set(Filter.class, Filter.BAND_PASS); + + // Convert to JSON + String json = toJSON(); + println("Settings JSON: " + json); + + // Restore defaults + restoreDefaults(); + + // Load from JSON + fromJSON(json); } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/OpenBCI_GUI/WidgetTemplateEnums.pde b/OpenBCI_GUI/WidgetTemplateEnums.pde new file mode 100644 index 000000000..ac73027b0 --- /dev/null +++ b/OpenBCI_GUI/WidgetTemplateEnums.pde @@ -0,0 +1,92 @@ +public enum TemplateDropdown1 implements IndexingInterface +{ + ITEM_A (0, 0, "Item A"), + ITEM_B (1, 1, "Item B"); + + private int index; + private int value; + private String label; + + TemplateDropdown1(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } +} + +public enum TemplateDropdown2 implements IndexingInterface +{ + ITEM_C (0, 0, "Item C"), + ITEM_D (1, 1, "Item D"), + ITEM_E (2, 2, "Item E"); + + private int index; + private int value; + private String label; + + TemplateDropdown2(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } +} + +public enum TemplateDropdown3 implements IndexingInterface +{ + ITEM_F (0, 0, "Item F"), + ITEM_G (1, 1, "Item G"), + ITEM_H (2, 2, "Item H"), + ITEM_I (3, 3, "Item I"); + + private int index; + private int value; + private String label; + + TemplateDropdown3(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/WidgetWithSettings.pde b/OpenBCI_GUI/WidgetWithSettings.pde new file mode 100644 index 000000000..e69de29bb From 3f9f6d672b16343a0838b2f8fc7bd1d8c513cd72 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Tue, 15 Apr 2025 15:04:40 -0500 Subject: [PATCH 14/29] Refactor and clean up SessionSettings.pde with initial Save/Load settings (WIP) --- OpenBCI_GUI/ControlPanel.pde | 22 +- OpenBCI_GUI/DataLogger.pde | 65 ++- OpenBCI_GUI/DataWriterBF.pde | 1 + OpenBCI_GUI/DataWriterODF.pde | 8 +- OpenBCI_GUI/EnumHelper.pde | 28 ++ OpenBCI_GUI/FileDurationEnum.pde | 32 ++ OpenBCI_GUI/OpenBCI_GUI.pde | 15 +- OpenBCI_GUI/SessionSettings.pde | 821 ++++--------------------------- OpenBCI_GUI/W_PacketLoss.pde | 2 +- OpenBCI_GUI/W_Spectrogram.pde | 68 ++- OpenBCI_GUI/W_Template.pde | 15 + OpenBCI_GUI/Widget.pde | 10 +- OpenBCI_GUI/WidgetManager.pde | 39 ++ OpenBCI_GUI/WidgetSettings.pde | 33 +- 14 files changed, 354 insertions(+), 805 deletions(-) create mode 100644 OpenBCI_GUI/EnumHelper.pde create mode 100644 OpenBCI_GUI/FileDurationEnum.pde diff --git a/OpenBCI_GUI/ControlPanel.pde b/OpenBCI_GUI/ControlPanel.pde index 687f42861..e50e9ea1e 100644 --- a/OpenBCI_GUI/ControlPanel.pde +++ b/OpenBCI_GUI/ControlPanel.pde @@ -266,7 +266,7 @@ class ControlPanel { sb.append("OpenBCISession_"); sb.append(dataLogger.getSessionName()); sb.append(File.separator); - sessionSettings.setSessionPath(sb.toString()); + dataLogger.setSessionPath(sb.toString()); } public void setBrainFlowStreamerOutput() { @@ -910,10 +910,8 @@ class SessionDataBox { createODFButton("odfButton", "OpenBCI", dataLogger.getDataLoggerOutputFormat(), x + padding, y + padding*2 + 18 + 58, (w-padding*3)/2, 24); createBDFButton("bdfButton", "BDF+", dataLogger.getDataLoggerOutputFormat(), x + padding*2 + (w-padding*3)/2, y + padding*2 + 18 + 58, (w-padding*3)/2, 24); - createMaxDurationDropdown("maxFileDuration", Arrays.asList(sessionSettings.fileDurations)); - - - + List fileDurationList = EnumHelper.getEnumStrings(OdfFileDuration.class); + createMaxDurationDropdown("maxFileDuration", fileDurationList, odfFileDuration); } public void update() { @@ -1012,10 +1010,10 @@ class SessionDataBox { }); } - private void createMaxDurationDropdown(String name, List _items){ + private void createMaxDurationDropdown(String name, List _items, OdfFileDuration defaultValue) { maxDurationDropdown = sessionData_cp5.addScrollableList(name) .setOpen(false) - .setColor(sessionSettings.dropdownColors) + .setColor(dropdownColorsGlobal) .setOutlineColor(150) //.setColorBackground(OPENBCI_BLUE) // text field bg color .setColorValueLabel(OPENBCI_DARKBLUE) // text color @@ -1033,7 +1031,7 @@ class SessionDataBox { maxDurationDropdown .getCaptionLabel() //the caption label is the text object in the primary bar .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!! - .setText(sessionSettings.fileDurations[sessionSettings.defaultOBCIMaxFileSize]) + .setText(defaultValue.getString()) .setFont(p4) .setSize(14) .getStyle() //need to grab style before affecting the paddingTop @@ -1042,7 +1040,7 @@ class SessionDataBox { maxDurationDropdown .getValueLabel() //the value label is connected to the text objects in the dropdown item bars .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!! - .setText(sessionSettings.fileDurations[sessionSettings.defaultOBCIMaxFileSize]) + .setText(defaultValue.getString()) .setFont(h5) .setSize(12) //set the font size of the item bars to 14pt .getStyle() //need to grab style before affecting the paddingTop @@ -1052,7 +1050,7 @@ class SessionDataBox { public void controlEvent(CallbackEvent theEvent) { if (theEvent.getAction() == ControlP5.ACTION_BROADCAST) { int n = (int)(theEvent.getController()).getValue(); - sessionSettings.setLogFileDurationChoice(n); + dataLogger.setLogFileDurationChoice(n); println("ControlPanel: Chosen Recording Duration: " + n); } else if (theEvent.getAction() == ControlP5.ACTION_ENTER) { lockOutsideElements(true); @@ -1775,7 +1773,7 @@ class BrainFlowStreamerBox { private void createDropdown(String name){ bfFileSaveOption = bfStreamerCp5.addScrollableList(name) .setOpen(false) - .setColor(sessionSettings.dropdownColors) + .setColor(dropdownColorsGlobal) .setOutlineColor(150) .setSize(167, (dataWriterBfEnum.values().length + 1) * 24) .setBarHeight(24) //height of top/primary bar @@ -2164,7 +2162,7 @@ class SDBox { sdList = cp5_sdBox.addScrollableList(name) .setOpen(false) - .setColor(sessionSettings.dropdownColors) + .setColor(dropdownColorsGlobal) .setOutlineColor(150) .setSize(w - padding*2, 2*24)//temporary size .setBarHeight(24) //height of top/primary bar diff --git a/OpenBCI_GUI/DataLogger.pde b/OpenBCI_GUI/DataLogger.pde index 7fc28e53a..9aac64fc7 100644 --- a/OpenBCI_GUI/DataLogger.pde +++ b/OpenBCI_GUI/DataLogger.pde @@ -8,6 +8,10 @@ class DataLogger { public final int OUTPUT_SOURCE_ODF = 1; // The OpenBCI CSV Data Format public final int OUTPUT_SOURCE_BDF = 2; // The BDF data format http://www.biosemi.com/faq/file_format.htm private int outputDataSource; + private String sessionPath = ""; + private boolean logFileIsOpen = false; + private long logFileStartTime; + private long logFileMaxDurationNano = -1; DataLogger() { //Default to OpenBCI CSV Data Format @@ -33,7 +37,7 @@ class DataLogger { private void saveNewData() { //If data is available, save to playback file... - if(!sessionSettings.isLogFileOpen()) { + if(!isLogFileOpen()) { return; } @@ -54,21 +58,21 @@ class DataLogger { } public void limitRecordingFileDuration() { - if (sessionSettings.isLogFileOpen() && outputDataSource == OUTPUT_SOURCE_ODF && sessionSettings.maxLogTimeReached()) { + if (isLogFileOpen() && outputDataSource == OUTPUT_SOURCE_ODF && maxLogTimeReached()) { println("DataLogging: Max recording duration reached for OpenBCI data format. Creating a new recording file in the session folder."); closeLogFile(); openNewLogFile(directoryManager.getFileNameDateTime()); - sessionSettings.setLogFileStartTime(System.nanoTime()); + setLogFileStartTime(System.nanoTime()); } } public void onStartStreaming() { if (outputDataSource > OUTPUT_SOURCE_NONE && eegDataSource != DATASOURCE_PLAYBACKFILE) { //open data file if it has not already been opened - if (!sessionSettings.isLogFileOpen()) { + if (!isLogFileOpen()) { openNewLogFile(directoryManager.getFileNameDateTime()); } - sessionSettings.setLogFileStartTime(System.nanoTime()); + setLogFileStartTime(System.nanoTime()); } //Print BrainFlow Streamer Info here after ODF and BDF println @@ -111,7 +115,7 @@ class DataLogger { // Do nothing... break; } - sessionSettings.setLogFileIsOpen(true); + setLogFileIsOpen(true); } /** @@ -159,7 +163,7 @@ class DataLogger { // Do nothing... break; } - sessionSettings.setLogFileIsOpen(false); + setLogFileIsOpen(false); } private void closeLogFileBDF() { @@ -188,22 +192,61 @@ class DataLogger { sessionName = s; } - public final String getSessionName() { + public String getSessionName() { return sessionName; } + + + public void setSessionPath (String _path) { + sessionPath = _path; + } + + public String getSessionPath() { + return sessionPath; + } + public void setBfWriterFolder(String _folderName, String _folderPath) { fileWriterBF.setBrainFlowStreamerFolderName(_folderName, _folderPath); } public void setBfWriterDefaultFolder() { - if (sessionSettings.getSessionPath() != "") { - sessionSettings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + sessionName); + if (getSessionPath() != "") { + setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + sessionName); } - fileWriterBF.setBrainFlowStreamerFolderName(sessionName, sessionSettings.getSessionPath()); + fileWriterBF.setBrainFlowStreamerFolderName(sessionName, getSessionPath()); } public String getBfWriterFilePath() { return fileWriterBF.getBrainFlowStreamerRecordingFileName(); } + + + private void setLogFileIsOpen(boolean _toggle) { + logFileIsOpen = _toggle; + } + + private boolean isLogFileOpen() { + return logFileIsOpen; + } + + private void setLogFileStartTime(long _time) { + logFileStartTime = _time; + verbosePrint("Settings: LogFileStartTime = " + _time); + } + + public void setLogFileDurationChoice(int n) { + int fileDurationMinutes = odfFileDuration.values()[n].getValue(); + logFileMaxDurationNano = fileDurationMinutes * 1000000000L * 60; + println("Settings: LogFileMaxDuration = " + fileDurationMinutes + " minutes"); + } + + //Only called during live mode && using OpenBCI Data Format + private boolean maxLogTimeReached() { + if (logFileMaxDurationNano < 0) { + return false; + } else { + return (System.nanoTime() - logFileStartTime) > (logFileMaxDurationNano); + } + } }; \ No newline at end of file diff --git a/OpenBCI_GUI/DataWriterBF.pde b/OpenBCI_GUI/DataWriterBF.pde index f4c136a18..6f7cbbed2 100644 --- a/OpenBCI_GUI/DataWriterBF.pde +++ b/OpenBCI_GUI/DataWriterBF.pde @@ -48,6 +48,7 @@ public class DataWriterBF { } public void setBrainFlowStreamerFolderName(String _folderName, String _folderPath) { + //FIX ME ? //sessionSettings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); folderName = _folderName; folderPath = _folderPath; diff --git a/OpenBCI_GUI/DataWriterODF.pde b/OpenBCI_GUI/DataWriterODF.pde index 2555ea9e4..e4c313247 100644 --- a/OpenBCI_GUI/DataWriterODF.pde +++ b/OpenBCI_GUI/DataWriterODF.pde @@ -7,8 +7,8 @@ public class DataWriterODF { protected String headerFirstLineString = "%OpenBCI Raw EXG Data"; DataWriterODF(String _sessionName, String _fileName) { - sessionSettings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); - fname = sessionSettings.getSessionPath(); + dataLogger.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); + fname = dataLogger.getSessionPath(); fname += fileNamePrependString; fname += _fileName; fname += ".txt"; @@ -21,8 +21,8 @@ public class DataWriterODF { DataWriterODF(String _sessionName, String _fileName, String _fileNamePrependString, String _headerFirstLineString) { fileNamePrependString = _fileNamePrependString; headerFirstLineString = _headerFirstLineString; - sessionSettings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); - fname = sessionSettings.getSessionPath(); + dataLogger.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); + fname = dataLogger.getSessionPath(); fname += fileNamePrependString; fname += _fileName; fname += ".txt"; diff --git a/OpenBCI_GUI/EnumHelper.pde b/OpenBCI_GUI/EnumHelper.pde new file mode 100644 index 000000000..68c9e8ae5 --- /dev/null +++ b/OpenBCI_GUI/EnumHelper.pde @@ -0,0 +1,28 @@ +//Used for Widget Dropdown Enums +interface IndexingInterface { + public int getIndex(); + public String getString(); +} + +/** + * Helper class for working with IndexingInterface enums + */ +public static class EnumHelper { + /** + * Generic method to get enum strings as a list + */ + public static List getListAsStrings(T[] values) { + List enumStrings = new ArrayList<>(); + for (T enumValue : values) { + enumStrings.add(enumValue.getString()); + } + return enumStrings; + } + + /** + * Get list of strings for an enum class that implements IndexingInterface + */ + public static & IndexingInterface> List getEnumStrings(Class enumClass) { + return getListAsStrings(enumClass.getEnumConstants()); + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/FileDurationEnum.pde b/OpenBCI_GUI/FileDurationEnum.pde new file mode 100644 index 000000000..acfd6dfcc --- /dev/null +++ b/OpenBCI_GUI/FileDurationEnum.pde @@ -0,0 +1,32 @@ +public enum OdfFileDuration implements IndexingInterface { + FIVE_MINUTES (0, 5, "5 Minutes"), + FIFTEEN_MINUTES (1, 15, "15 Minutes"), + THIRTY_MINUTES (2, 30, "30 Minutes"), + SIXTY_MINUTES (3, 60, "60 Minutes"), + ONE_HUNDRED_TWENTY_MINUTES (4, 120, "120 Minutes"), + NO_LIMIT (5, -1, "No Limit"); + + private int index; + private int duration; + private String label; + + OdfFileDuration(int _index, int _duration, String _label) { + this.index = _index; + this.duration = _duration; + this.label = _label; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } + + public int getValue() { + return duration; + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde index f2145c249..e0b4b28b9 100644 --- a/OpenBCI_GUI/OpenBCI_GUI.pde +++ b/OpenBCI_GUI/OpenBCI_GUI.pde @@ -121,6 +121,7 @@ String sdData_fname = "N/A"; //only used if loading input data from a sd file DataSource currentBoard = new BoardNull(); DataLogger dataLogger = new DataLogger(); +OdfFileDuration odfFileDuration = OdfFileDuration.SIXTY_MINUTES; // Intialize interface protocols InterfaceSerial iSerial = new InterfaceSerial(); //This is messy, half-deprecated code. See comments in InterfaceSerial.pde - Nov. 2020 @@ -256,6 +257,8 @@ final color SIGNAL_CHECK_YELLOW = color(221, 178, 13); //Same color as yellow ch final color SIGNAL_CHECK_YELLOW_LOWALPHA = color(221, 178, 13, 150); final color SIGNAL_CHECK_RED = BOLD_RED; final color SIGNAL_CHECK_RED_LOWALPHA = color(224, 56, 45, 150); +public CColor dropdownColorsGlobal = new CColor(); + //Channel Colors -- Defaulted to matching the OpenBCI electrode ribbon cable //Channel Colors -- Defaulted to matching the OpenBCI electrode ribbon cable @@ -379,6 +382,13 @@ void setup() { openbciLogoCog = loadImage("obci-logo-blu-cog.png"); + dropdownColorsGlobal.setActive((int)BUTTON_PRESSED); //bg color of box when pressed + dropdownColorsGlobal.setForeground((int)BUTTON_HOVER); //when hovering over any box (primary or dropdown) + dropdownColorsGlobal.setBackground((int)color(255)); //bg color of boxes (including primary) + dropdownColorsGlobal.setCaptionLabel((int)color(1, 18, 41)); //color of text in primary box + // dropdownColorsGlobal.setValueLabel((int)color(1, 18, 41)); //color of text in all dropdown boxes + dropdownColorsGlobal.setValueLabel((int)color(100)); //color of text in all dropdown boxes + // check if the current directory is writable File dummy = new File(sketchPath()); if (!dummy.canWrite()) { @@ -1063,7 +1073,6 @@ void systemInitSession() { //Global function to update the number of channels void updateGlobalChannelCount(int _channelCount) { globalChannelCount = _channelCount; - sessionSettings.sessionSettingsChannelCount = _channelCount; //used in SoftwareSettings.pde only fftBuff = new ddf.minim.analysis.FFT[globalChannelCount]; //reinitialize the FFT buffer println("OpenBCI_GUI: Channel count set to " + str(globalChannelCount)); } @@ -1076,7 +1085,7 @@ void introAnimation() { float transparency = 0; if (millis() >= sessionSettings.introAnimationInit) { - transparency = map(millis() - sessionSettings.introAnimationInit, t1, sessionSettings.introAnimationDuration, 0, 255); + transparency = map(millis() - sessionSettings.introAnimationInit, t1, sessionSettings.INTRO_ANIMATION_DURATION, 0, 255); verbosePrint(String.valueOf(transparency)); tint(255, transparency); //draw OpenBCI Logo Front & Center @@ -1090,7 +1099,7 @@ void introAnimation() { } //Exit intro animation when the duration has expired AND the Control Panel is ready - if ((millis() >= sessionSettings.introAnimationInit + sessionSettings.introAnimationDuration) + if ((millis() >= sessionSettings.introAnimationInit + sessionSettings.INTRO_ANIMATION_DURATION) && controlPanel != null) { systemMode = SYSTEMMODE_PREINIT; controlPanel.open(); diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde index 95428a1a8..2884e84c5 100644 --- a/OpenBCI_GUI/SessionSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -1,5 +1,5 @@ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/* +// // This sketch saves and loads User Settings that appear during Sessions. // -- All Time Series widget settings in Live, Playback, and Synthetic modes // -- All FFT widget settings @@ -28,38 +28,24 @@ // -- Example2: GUI version and settings version // -- Requires new JSON key 'version` and settingsVersion // -*/ +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////// -// SessionSettings Class // -///////////////////////////////// class SessionSettings { //Current version to save to JSON - String settingsVersion = "4.0.0"; + private String settingsVersion = "5.0.0"; //for screen resizing - boolean screenHasBeenResized = false; - float timeOfLastScreenResize = 0; - int widthOfLastScreen = 0; - int heightOfLastScreen = 0; + public boolean screenHasBeenResized = false; + public float timeOfLastScreenResize = 0; + public int widthOfLastScreen = 0; + public int heightOfLastScreen = 0; //default layout variables - int currentLayout; + public int currentLayout; //Used to time the GUI intro animation - int introAnimationInit = 0; - final int introAnimationDuration = 2500; - //Max File Size #461, default option 4 -> 60 minutes - public final String[] fileDurations = {"5 Minutes", "15 minutes", "30 Minutes", "60 Minutes", "120 Minutes", "No Limit"}; - public final int[] fileDurationInts = {5, 15, 30, 60, 120, -1}; - public final int defaultOBCIMaxFileSize = 3; //4th option from the above list - private boolean logFileIsOpen = false; - private long logFileStartTime; - private long logFileMaxDurationNano = -1; - //this is a global CColor that determines the style of all widget dropdowns ... this should go in WidgetManager.pde - public CColor dropdownColors = new CColor(); + public int introAnimationInit = 0; + public final int INTRO_ANIMATION_DURATION = 2500; - //default configuration settings file location and file name variables - private String sessionPath = ""; - final String[] userSettingsFiles = { + private final String[] USER_SETTINGS_FILES = { "CytonUserSettings.json", "DaisyUserSettings.json", "GanglionUserSettings.json", @@ -68,7 +54,7 @@ class SessionSettings { "SynthEightUserSettings.json", "SynthSixteenUserSettings.json" }; - final String[] defaultSettingsFiles = { + private final String[] DEFAULT_SETTINGS_FILES = { "CytonDefaultSettings.json", "DaisyDefaultSettings.json", "GanglionDefaultSettings.json", @@ -78,88 +64,22 @@ class SessionSettings { "SynthSixteenDefaultSettings.json" }; - //Load Accel. dropdown variables - int loadAccelVertScale; - int loadAccelHorizScale; - - //Load Analog Read dropdown variables - int loadAnalogReadVertScale; - int loadAnalogReadHorizScale; - - //Load FFT dropdown variables - int fftMaxFrqLoad; - int fftMaxuVLoad; - int fftLogLinLoad; - int fftSmoothingLoad; - int fftFilterLoad; - - //Load Headplot dropdown variables - int hpIntensityLoad; - int hpPolarityLoad; - int hpContoursLoad; - int hpSmoothingLoad; - - //Band Power widget settings - //smoothing and filter dropdowns are linked to FFT, so no need to save again - List loadBPActiveChans = new ArrayList(); - int loadBPAutoClean; - int loadBPAutoCleanThreshold; - int loadBPAutoCleanTimer; - - //Spectrogram widget settings - List loadSpectActiveChanTop = new ArrayList(); - List loadSpectActiveChanBot = new ArrayList(); - int spectMaxFrqLoad; - int spectSampleRateLoad; - int spectLogLinLoad; - - //Networking Settings save/load variables - JSONObject loadNetworkingSettings; - - //EMG Widget - List loadEmgActiveChannels = new ArrayList(); - - //EMG Joystick Widget - int loadEmgJoystickSmoothing; - List loadEmgJoystickInputs = new ArrayList(); - - //Marker Widget - private int loadMarkerWindow; - private int loadMarkerVertScale; - - //Focus Widget - private int loadFocusMetric; - private int loadFocusThreshold; - private int loadFocusWindow; - //Primary JSON objects for saving and loading data private JSONObject saveSettingsJSONData; private JSONObject loadSettingsJSONData; - private final String kJSONKeyDataInfo = "dataInfo"; - private final String kJSONKeyTimeSeries = "timeSeries"; - private final String kJSONKeySettings = "settings"; - private final String kJSONKeyFFT = "fft"; - private final String kJSONKeyAccel = "accelerometer"; - private final String kJSONKeyNetworking = "networking"; - private final String kJSONKeyHeadplot = "headplot"; - private final String kJSONKeyBandPower = "bandPower"; - private final String kJSONKeyWidget = "widget"; - private final String kJSONKeyVersion = "version"; - private final String kJSONKeySpectrogram = "spectrogram"; - private final String kJSONKeyEmg = "emg"; - private final String kJSONKeyEmgJoystick = "emgJoystick"; - private final String kJSONKeyMarker = "marker"; - private final String kJSONKeyFocus = "focus"; + private final String GLOBAL_SETTINGS_KEY = "globalSettings"; + private final String GUI_VERSION_KEY = "guiVersion"; + private final String SESSION_SETTINGS_VERSION_KEY = "sessionSettingsVersion"; + private final String CHANNEL_COUNT_KEY = "channelCount"; + private final String DATA_SOURCE_KEY = "dataSource"; + private final String DATA_SMOOTHING_KEY = "dataSmoothing"; + private final String WIDGET_LAYOUT_KEY = "widgetLayout"; + private final String NETWORKING_KEY = "networking"; + private final String WIDGET_CONTAINER_SETTINGS_KEY = "widgetContainerSettings"; + private final String WIDGET_SETTINGS_KEY = "widgetSettings"; - //used only in this class to count the number of channels being used while saving/loading, this gets updated in updateGlobalChannelCount whenever the number of channels being used changes - int sessionSettingsChannelCount; - int numChanloaded; boolean chanNumError = false; - int numLoadedWidgets; - String [] loadedWidgetsArray; - int loadFramerate; - int loadDatasource; boolean dataSourceError = false; String saveDialogName; //Used when Save button is pressed @@ -172,55 +92,7 @@ class SessionSettings { final int initTimeoutThreshold = 12000; //Timeout threshold in milliseconds SessionSettings() { - //Instantiated on app start in OpenBCI_GUI.pde - dropdownColors.setActive((int)BUTTON_PRESSED); //bg color of box when pressed - dropdownColors.setForeground((int)BUTTON_HOVER); //when hovering over any box (primary or dropdown) - dropdownColors.setBackground((int)color(255)); //bg color of boxes (including primary) - dropdownColors.setCaptionLabel((int)color(1, 18, 41)); //color of text in primary box - // dropdownColors.setValueLabel((int)color(1, 18, 41)); //color of text in all dropdown boxes - dropdownColors.setValueLabel((int)color(100)); //color of text in all dropdown boxes - - setLogFileDurationChoice(defaultOBCIMaxFileSize); - } - - /////////////////////////////////// - // OpenBCI Data Format Functions // - /////////////////////////////////// - - public void setLogFileIsOpen (boolean _toggle) { - logFileIsOpen = _toggle; - } - - public boolean isLogFileOpen() { - return logFileIsOpen; - } - - public void setLogFileStartTime(long _time) { - logFileStartTime = _time; - verbosePrint("Settings: LogFileStartTime = " + _time); - } - - public void setLogFileDurationChoice(int choice) { - logFileMaxDurationNano = fileDurationInts[choice] * 1000000000L * 60; - println("Settings: LogFileMaxDuration = " + fileDurationInts[choice] + " minutes"); - } - - //Only called during live mode && using OpenBCI Data Format - public boolean maxLogTimeReached() { - if (logFileMaxDurationNano < 0) { - return false; - } else { - return (System.nanoTime() - logFileStartTime) > (logFileMaxDurationNano); - } - } - - public void setSessionPath (String _path) { - sessionPath = _path; - } - - public String getSessionPath() { - //println("SESSIONPATH==",sessionPath, millis()); - return sessionPath; + //Constructor } //////////////////////////////////////////////////////////////// @@ -230,10 +102,6 @@ class SessionSettings { //////////////////////////////////////////////////////////////// void init() { String defaultSettingsFileToSave = getPath("Default", eegDataSource, globalChannelCount); - int defaultNumChanLoaded = 0; - int defaultLoadedDataSource = 0; - String defaultSettingsVersion = ""; - String defaultGUIVersion = ""; //Take a snapshot of the default GUI settings on every system init println("InitSettings: Saving Default Settings to file!"); @@ -250,202 +118,27 @@ class SessionSettings { /////////////////////////////// void save(String saveGUISettingsFileLocation) { - //Set up a JSON array + // Set up a JSON array saveSettingsJSONData = new JSONObject(); - //Save the number of channels being used and eegDataSource in the first object - JSONObject saveNumChannelsData = new JSONObject(); - saveNumChannelsData.setInt("Channels", sessionSettingsChannelCount); - saveNumChannelsData.setInt("Data Source", eegDataSource); - //println("Settings: NumChan: " + sessionSettingsChannelCount); - saveSettingsJSONData.setJSONObject(kJSONKeyDataInfo, saveNumChannelsData); - - //Make a new JSON Object for Time Series Settings - JSONObject saveTSSettings = new JSONObject(); - //FIX ME - /* - saveTSSettings.setInt("Time Series Vert Scale", w_timeSeries.getVerticalScale().getIndex()); - saveTSSettings.setInt("Time Series Horiz Scale", w_timeSeries.getHorizontalScale().getIndex()); - saveTSSettings.setInt("Time Series Label Mode", w_timeSeries.getLabelMode().getIndex()); - //Save data from the Active channel checkBoxes - JSONArray saveActiveChanTS = new JSONArray(); - int numActiveTSChan = w_timeSeries.tsChanSelect.getActiveChannels().size(); - for (int i = 0; i < numActiveTSChan; i++) { - int activeChannel = w_timeSeries.tsChanSelect.getActiveChannels().get(i); - saveActiveChanTS.setInt(i, activeChannel); - } - saveTSSettings.setJSONArray("activeChannels", saveActiveChanTS); - */ - saveSettingsJSONData.setJSONObject(kJSONKeyTimeSeries, saveTSSettings); - - //Make a second JSON object within our JSONArray to store Global settings for the GUI + // Global Settings JSONObject saveGlobalSettings = new JSONObject(); - saveGlobalSettings.setInt("Current Layout", currentLayout); - //FIX ME - /* - saveGlobalSettings.setInt("Analog Read Vert Scale", arVertScaleSave); - saveGlobalSettings.setInt("Analog Read Horiz Scale", arHorizScaleSave); - */ + saveGlobalSettings.setString(GUI_VERSION_KEY, localGUIVersionString); + saveGlobalSettings.setString(SESSION_SETTINGS_VERSION_KEY, settingsVersion); + saveGlobalSettings.setInt(CHANNEL_COUNT_KEY, globalChannelCount); + saveGlobalSettings.setInt(DATA_SOURCE_KEY, eegDataSource); if (currentBoard instanceof SmoothingCapableBoard) { - saveGlobalSettings.setBoolean("Data Smoothing", ((SmoothingCapableBoard)currentBoard).getSmoothingActive()); + saveGlobalSettings.setBoolean(DATA_SMOOTHING_KEY, ((SmoothingCapableBoard)currentBoard).getSmoothingActive()); } - saveSettingsJSONData.setJSONObject(kJSONKeySettings, saveGlobalSettings); - - /////Setup JSON Object for gui version and settings Version - JSONObject saveVersionInfo = new JSONObject(); - saveVersionInfo.setString("gui", localGUIVersionString); - saveVersionInfo.setString("settings", settingsVersion); - saveSettingsJSONData.setJSONObject(kJSONKeyVersion, saveVersionInfo); + saveGlobalSettings.setInt(WIDGET_LAYOUT_KEY, currentLayout); + saveSettingsJSONData.setJSONObject(GLOBAL_SETTINGS_KEY, saveGlobalSettings); - ///////////////////////////////////////////////Setup new JSON object to save FFT settings - JSONObject saveFFTSettings = new JSONObject(); + // Networking Settings + JSONObject saveNetworkingSettings = parseJSONObject(dataProcessing.networkingSettings.getJson()); + saveSettingsJSONData.setJSONObject(NETWORKING_KEY, saveNetworkingSettings); - //FIX ME - /* - //Save FFT_Max Freq Setting. The max frq variable is updated every time the user selects a dropdown in the FFT widget - saveFFTSettings.setInt("FFT_Max Freq", fftMaxFrqSave); - //Save FFT_Max uV Setting. The max uV variable is updated also when user selects dropdown in the FFT widget - saveFFTSettings.setInt("FFT_Max uV", fftMaxuVSave); - //Save FFT_LogLin Setting. Same thing happens for LogLin - saveFFTSettings.setInt("FFT_LogLin", fftLogLinSave); - //Save FFT_Smoothing Setting - saveFFTSettings.setInt("FFT_Smoothing", fftSmoothingSave); - //Save FFT_Filter Setting - if (isFFTFiltered == true) fftFilterSave = 0; - if (isFFTFiltered == false) fftFilterSave = 1; - saveFFTSettings.setInt("FFT_Filter", fftFilterSave); - */ - //Set the FFT JSON Object - saveSettingsJSONData.setJSONObject(kJSONKeyFFT, saveFFTSettings); //next object will be set to sessionSettingsChannelCount+3, etc. - - ///////////////////////////////////////////////Setup new JSON object to save Accelerometer settings - //FIX ME - /* - if (w_accelerometer != null) { - JSONObject saveAccSettings = new JSONObject(); - saveAccSettings.setInt("Accelerometer Vert Scale", accVertScaleSave); - saveAccSettings.setInt("Accelerometer Horiz Scale", accHorizScaleSave); - saveSettingsJSONData.setJSONObject(kJSONKeyAccel, saveAccSettings); - } - */ - - ///////////////////////////////////////////////Save Networking settings - String nwSettingsValues = dataProcessing.networkingSettings.getJson(); - JSONObject saveNetworkingSettings = parseJSONObject(nwSettingsValues); - saveSettingsJSONData.setJSONObject(kJSONKeyNetworking, saveNetworkingSettings); - - ///////////////////////////////////////////////Setup new JSON object to save Headplot settings - //if (w_headPlot != null) { - JSONObject saveHeadplotSettings = new JSONObject(); - - //FIX ME - /* - //Save Headplot Intesity - saveHeadplotSettings.setInt("HP_intensity", hpIntensitySave); - //Save Headplot Polarity - saveHeadplotSettings.setInt("HP_polarity", hpPolaritySave); - //Save Headplot contours - saveHeadplotSettings.setInt("HP_contours", hpContoursSave); - //Save Headplot Smoothing Setting - saveHeadplotSettings.setInt("HP_smoothing", hpSmoothingSave); - //Set the Headplot JSON Object - */ - saveSettingsJSONData.setJSONObject(kJSONKeyHeadplot, saveHeadplotSettings); - //} - - ///////////////////////////////////////////////Setup new JSON object to save Band Power settings - JSONObject saveBPSettings = new JSONObject(); - - /* - //FIX ME - //Save data from the Active channel checkBoxes - JSONArray saveActiveChanBP = new JSONArray(); - int numActiveBPChan = w_bandPower.bpChanSelect.getActiveChannels().size(); - for (int i = 0; i < numActiveBPChan; i++) { - int activeChannel = w_bandPower.bpChanSelect.getActiveChannels().get(i); - saveActiveChanBP.setInt(i, activeChannel); - } - saveBPSettings.setJSONArray("activeChannels", saveActiveChanBP); - saveBPSettings.setInt("bpAutoClean", w_bandPower.getAutoClean().getIndex()); - saveBPSettings.setInt("bpAutoCleanThreshold", w_bandPower.getAutoCleanThreshold().getIndex()); - saveBPSettings.setInt("bpAutoCleanTimer", w_bandPower.getAutoCleanTimer().getIndex()); - */ - saveSettingsJSONData.setJSONObject(kJSONKeyBandPower, saveBPSettings); - - ///////////////////////////////////////////////Setup new JSON object to save Spectrogram settings - JSONObject saveSpectrogramSettings = new JSONObject(); - //Save data from the Active channel checkBoxes - Top - //JSONArray saveActiveChanSpectTop = new JSONArray(); - /* - //FIX ME - int numActiveSpectChanTop = w_spectrogram.spectChanSelectTop.getActiveChannels().size(); - for (int i = 0; i < numActiveSpectChanTop; i++) { - int activeChannel = w_spectrogram.spectChanSelectTop.getActiveChannels().get(i); - saveActiveChanSpectTop.setInt(i, activeChannel); - } - saveSpectrogramSettings.setJSONArray("activeChannelsTop", saveActiveChanSpectTop); - //Save data from the Active channel checkBoxes - Bottom - JSONArray saveActiveChanSpectBot = new JSONArray(); - int numActiveSpectChanBot = w_spectrogram.spectChanSelectBot.getActiveChannels().size(); - for (int i = 0; i < numActiveSpectChanBot; i++) { - int activeChannel = w_spectrogram.spectChanSelectBot.getActiveChannels().get(i); - saveActiveChanSpectBot.setInt(i, activeChannel); - } - */ - //saveSpectrogramSettings.setJSONArray("activeChannelsBot", saveActiveChanSpectBot); - //Save Spectrogram_Max Freq Setting. The max frq variable is updated every time the user selects a dropdown in the spectrogram widget - //FIX ME - /* - saveSpectrogramSettings.setInt("Spectrogram_Max Freq", spectMaxFrqSave); - saveSpectrogramSettings.setInt("Spectrogram_Sample Rate", spectSampleRateSave); - saveSpectrogramSettings.setInt("Spectrogram_LogLin", spectLogLinSave); - */ - saveSettingsJSONData.setJSONObject(kJSONKeySpectrogram, saveSpectrogramSettings); - - ///////////////////////////////////////////////Setup new JSON object to save EMG Settings - JSONObject saveEMGSettings = new JSONObject(); - - //Save data from the Active channel checkBoxes - JSONArray saveActiveChanEMG = new JSONArray(); - //FIX ME - /* - int numActiveEMGChan = w_emg.emgChannelSelect.getActiveChannels().size(); - for (int i = 0; i < numActiveEMGChan; i++) { - int activeChannel = w_emg.emgChannelSelect.getActiveChannels().get(i); - saveActiveChanEMG.setInt(i, activeChannel); - } - */ - saveEMGSettings.setJSONArray("activeChannels", saveActiveChanEMG); - saveSettingsJSONData.setJSONObject(kJSONKeyEmg, saveEMGSettings); - - /* - //FIX ME - ///////////////////////////////////////////////Setup new JSON object to save EMG Joystick Settings - JSONObject saveEmgJoystickSettings = new JSONObject(); - saveEmgJoystickSettings.setInt("smoothing", w_emgJoystick.joystickSmoothing.getIndex()); - JSONArray saveEmgJoystickInputs = new JSONArray(); - for (int i = 0; i < w_emgJoystick.getNumEMGInputs(); i++) { - saveEmgJoystickInputs.setInt(i, w_emgJoystick.emgJoystickInputs.getInput(i).getIndex()); - } - saveEmgJoystickSettings.setJSONArray("joystickInputs", saveEmgJoystickInputs); - saveSettingsJSONData.setJSONObject(kJSONKeyEmgJoystick, saveEmgJoystickSettings); - - ///////////////////////////////////////////////Setup new JSON object to save Marker Widget Settings - JSONObject saveMarkerSettings = new JSONObject(); - saveMarkerSettings.setInt("markerWindow", w_marker.getMarkerWindow().getIndex()); - saveMarkerSettings.setInt("markerVertScale", w_marker.getMarkerVertScale().getIndex()); - saveSettingsJSONData.setJSONObject(kJSONKeyMarker, saveMarkerSettings); - - ///////////////////////////////////////////////Setup new JSON object to save Marker Widget Settings - JSONObject saveFocusSettings = new JSONObject(); - saveFocusSettings.setInt("focusMetric", w_focus.getFocusMetric().getIndex()); - saveFocusSettings.setInt("focusThreshold", w_focus.getFocusThreshold().getIndex()); - saveFocusSettings.setInt("focusWindow", w_focus.getFocusWindow().getIndex()); - saveSettingsJSONData.setJSONObject(kJSONKeyFocus, saveFocusSettings); - */ - - ///////////////////////////////////////////////Setup new JSON object to save Widgets Active in respective Containers - JSONObject saveWidgetSettings = new JSONObject(); + // Widget layout settings + JSONObject saveWidgetLayout = new JSONObject(); int numActiveWidgets = 0; //Save what Widgets are active and respective Container number (see Containers.pde) @@ -456,31 +149,24 @@ class SessionSettings { // activeWidgets.add(i); //keep track of the active widget int containerCountsave = widgetManager.widgets.get(i).currentContainer; //println("Widget " + i + " is in Container " + containerCountsave); - saveWidgetSettings.setInt("Widget_"+i, containerCountsave); + saveWidgetLayout.setInt("Widget_"+i, containerCountsave); } else if (!widgetManager.widgets.get(i).getIsActive()) { //If a widget is not active... - saveWidgetSettings.remove("Widget_"+i); //remove non-active widget from JSON + saveWidgetLayout.remove("Widget_"+i); //remove non-active widget from JSON //println("widget"+i+" is not active"); } } println("SessionSettings: " + numActiveWidgets + " active widgets saved!"); - //Print what widgets are in the containers used by current layout for only the number of active widgets - //for (int i = 0; i < numActiveWidgets; i++) { - //int containerCounter = widgetManager.layouts.get(currentLayout).containerInts[i]; - //println("Container " + containerCounter + " is available"); //For debugging - //} - saveSettingsJSONData.setJSONObject(kJSONKeyWidget, saveWidgetSettings); + saveSettingsJSONData.setJSONObject(WIDGET_CONTAINER_SETTINGS_KEY, saveWidgetLayout); - ///////////////////////////////////////////////////////////////////////////////// - ///ADD more global settings above this line in the same formats as above///////// + // Settings for all widgets + JSONObject saveWidgetSettings = parseJSONObject(widgetManager.getWidgetSettingsAsJson()); + saveSettingsJSONData.setJSONObject(WIDGET_SETTINGS_KEY, saveWidgetSettings); //Let's save the JSON array to a file! saveJSONObject(saveSettingsJSONData, saveGUISettingsFileLocation); - } //End of Save GUI Settings function + } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Load GUI Settings // - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void load(String loadGUISettingsFileLocation) throws Exception { //Load all saved User Settings from a JSON file if it exists loadSettingsJSONData = loadJSONObject(loadGUISettingsFileLocation); @@ -488,19 +174,19 @@ class SessionSettings { verbosePrint(loadSettingsJSONData.toString()); //Check the number of channels saved to json first! - JSONObject loadDataSettings = loadSettingsJSONData.getJSONObject(kJSONKeyDataInfo); - numChanloaded = loadDataSettings.getInt("Channels"); + JSONObject loadGlobalSettings = loadSettingsJSONData.getJSONObject(GLOBAL_SETTINGS_KEY); + int numChanloaded = loadGlobalSettings.getInt(CHANNEL_COUNT_KEY); //Print error if trying to load a different number of channels - if (numChanloaded != sessionSettingsChannelCount) { - println("Channels being loaded from " + loadGUISettingsFileLocation + " don't match channels being used!"); + if (numChanloaded != globalChannelCount) { + println("SessionSettings: Channels being loaded from " + loadGUISettingsFileLocation + " don't match channels being used!"); chanNumError = true; throw new Exception(); } else { chanNumError = false; } //Check the Data Source integer next: Cyton = 0, Ganglion = 1, Playback = 2, Synthetic = 3 - loadDatasource = loadDataSettings.getInt("Data Source"); - verbosePrint("loadGUISettings: Data source loaded: " + loadDatasource + ". Current data source: " + eegDataSource); + int loadDatasource = loadGlobalSettings.getInt(DATA_SOURCE_KEY); + verbosePrint("SessionSettings: Data source loaded: " + loadDatasource + ". Current data source: " + eegDataSource); //Print error if trying to load a different data source (ex. Live != Synthetic) if (loadDatasource != eegDataSource) { println("Data source being loaded from " + loadGUISettingsFileLocation + " doesn't match current data source."); @@ -510,397 +196,58 @@ class SessionSettings { dataSourceError = false; } - //get the global settings JSON object - JSONObject loadGlobalSettings = loadSettingsJSONData.getJSONObject(kJSONKeySettings); - //Store loaded layout to current layout variable - currentLayout = loadGlobalSettings.getInt("Current Layout"); - //FIX ME - /* - loadAnalogReadVertScale = loadGlobalSettings.getInt("Analog Read Vert Scale"); - loadAnalogReadHorizScale = loadGlobalSettings.getInt("Analog Read Horiz Scale"); - */ - //Load more global settings after this line, if needed - Boolean loadDataSmoothingSetting = (currentBoard instanceof SmoothingCapableBoard) ? loadGlobalSettings.getBoolean("Data Smoothing") : null; - - //get the FFT settings - //FIX ME - /* - JSONObject loadFFTSettings = loadSettingsJSONData.getJSONObject(kJSONKeyFFT); - fftMaxFrqLoad = loadFFTSettings.getInt("FFT_Max Freq"); - fftMaxuVLoad = loadFFTSettings.getInt("FFT_Max uV"); - fftLogLinLoad = loadFFTSettings.getInt("FFT_LogLin"); - fftSmoothingLoad = loadFFTSettings.getInt("FFT_Smoothing"); - fftFilterLoad = loadFFTSettings.getInt("FFT_Filter"); - */ - - //FIX ME - /* - //get the Accelerometer settings - if (w_accelerometer != null) { - JSONObject loadAccSettings = loadSettingsJSONData.getJSONObject(kJSONKeyAccel); - loadAccelVertScale = loadAccSettings.getInt("Accelerometer Vert Scale"); - loadAccelHorizScale = loadAccSettings.getInt("Accelerometer Horiz Scale"); - } - */ - - //get the Networking Settings - loadNetworkingSettings = loadSettingsJSONData.getJSONObject(kJSONKeyNetworking); - - //get the Headplot settings - //if (w_headPlot != null) { - //FIX ME - /* - JSONObject loadHeadplotSettings = loadSettingsJSONData.getJSONObject(kJSONKeyHeadplot); - hpIntensityLoad = loadHeadplotSettings.getInt("HP_intensity"); - hpPolarityLoad = loadHeadplotSettings.getInt("HP_polarity"); - hpContoursLoad = loadHeadplotSettings.getInt("HP_contours"); - hpSmoothingLoad = loadHeadplotSettings.getInt("HP_smoothing"); - */ - //} - - //Get Band Power widget settings - //FIX ME - /* - loadBPActiveChans.clear(); - JSONObject loadBPSettings = loadSettingsJSONData.getJSONObject(kJSONKeyBandPower); - JSONArray loadBPChan = loadBPSettings.getJSONArray("activeChannels"); - for (int i = 0; i < loadBPChan.size(); i++) { - loadBPActiveChans.add(loadBPChan.getInt(i)); - } - loadBPAutoClean = loadBPSettings.getInt("bpAutoClean"); - loadBPAutoCleanThreshold = loadBPSettings.getInt("bpAutoCleanThreshold"); - loadBPAutoCleanTimer = loadBPSettings.getInt("bpAutoCleanTimer"); - //println("Settings: band power active chans loaded = " + loadBPActiveChans ); - */ - - try { - //Get Spectrogram widget settings - loadSpectActiveChanTop.clear(); - loadSpectActiveChanBot.clear(); - JSONObject loadSpectSettings = loadSettingsJSONData.getJSONObject(kJSONKeySpectrogram); - JSONArray loadSpectChanTop = loadSpectSettings.getJSONArray("activeChannelsTop"); - for (int i = 0; i < loadSpectChanTop.size(); i++) { - loadSpectActiveChanTop.add(loadSpectChanTop.getInt(i)); - } - JSONArray loadSpectChanBot = loadSpectSettings.getJSONArray("activeChannelsBot"); - for (int i = 0; i < loadSpectChanBot.size(); i++) { - loadSpectActiveChanBot.add(loadSpectChanBot.getInt(i)); - } - spectMaxFrqLoad = loadSpectSettings.getInt("Spectrogram_Max Freq"); - spectSampleRateLoad = loadSpectSettings.getInt("Spectrogram_Sample Rate"); - spectLogLinLoad = loadSpectSettings.getInt("Spectrogram_LogLin"); - //println(loadSpectActiveChanTop, loadSpectActiveChanBot); - } catch (Exception e) { - e.printStackTrace(); - } - - //Get EMG widget settings - loadEmgActiveChannels.clear(); - JSONObject loadEmgSettings = loadSettingsJSONData.getJSONObject(kJSONKeyEmg); - JSONArray loadEmgChan = loadEmgSettings.getJSONArray("activeChannels"); - for (int i = 0; i < loadEmgChan.size(); i++) { - loadEmgActiveChannels.add(loadEmgChan.getInt(i)); - } - - //Get EMG Joystick widget settings - JSONObject loadEmgJoystickSettings = loadSettingsJSONData.getJSONObject(kJSONKeyEmgJoystick); - loadEmgJoystickSmoothing = loadEmgJoystickSettings.getInt("smoothing"); - loadEmgJoystickInputs.clear(); - JSONArray loadJoystickInputsJson = loadEmgJoystickSettings.getJSONArray("joystickInputs"); - for (int i = 0; i < loadJoystickInputsJson.size(); i++) { - loadEmgJoystickInputs.add(loadJoystickInputsJson.getInt(i)); + if (currentBoard instanceof SmoothingCapableBoard) { + Boolean loadDataSmoothingSetting = loadGlobalSettings.getBoolean(DATA_SMOOTHING_KEY); + ((SmoothingCapableBoard)currentBoard).setSmoothingActive(loadDataSmoothingSetting); + topNav.updateSmoothingButtonText(); } - //Get Marker widget settings - JSONObject loadMarkerSettings = loadSettingsJSONData.getJSONObject(kJSONKeyMarker); - loadMarkerWindow = loadMarkerSettings.getInt("markerWindow"); - loadMarkerVertScale = loadMarkerSettings.getInt("markerVertScale"); + // Layout Settings + currentLayout = loadGlobalSettings.getInt(WIDGET_LAYOUT_KEY); - //Get Focus widget settings - JSONObject loadFocusSettings = loadSettingsJSONData.getJSONObject(kJSONKeyFocus); - loadFocusMetric = loadFocusSettings.getInt("focusMetric"); - loadFocusThreshold = loadFocusSettings.getInt("focusThreshold"); - loadFocusWindow = loadFocusSettings.getInt("focusWindow"); + // Networking Settings + JSONObject networkingSettingsJson = loadSettingsJSONData.getJSONObject(NETWORKING_KEY); + dataProcessing.networkingSettings.loadJson(networkingSettingsJson.toString()); - //get the Widget/Container settings - JSONObject loadWidgetSettings = loadSettingsJSONData.getJSONObject(kJSONKeyWidget); + // Widget Layout Settings + JSONObject widgetContainerSettings = loadSettingsJSONData.getJSONObject(WIDGET_CONTAINER_SETTINGS_KEY); //Apply Layout directly before loading and applying widgets to containers widgetManager.setNewContainerLayout(currentLayout); - verbosePrint("LoadGUISettings: Layout " + currentLayout + " Loaded!"); - numLoadedWidgets = loadWidgetSettings.size(); - + verbosePrint("SessionSettings: Layout " + currentLayout + " Loaded!"); + int numLoadedWidgets = widgetContainerSettings.size(); //int numActiveWidgets = 0; //reset the counter - for (int w = 0; w < widgetManager.widgets.size(); w++) { //increment through all widgets - if (widgetManager.widgets.get(w).getIsActive()) { //If a widget is active... - verbosePrint("Deactivating widget [" + w + "]"); - widgetManager.widgets.get(w).setIsActive(false); - //numActiveWidgets++; //counter the number of de-activated widgets + for (int i = 0; i < widgetManager.widgets.size(); i++) { //increment through all widgets + if (widgetManager.widgets.get(i).getIsActive()) { //If a widget is active... + widgetManager.widgets.get(i).setIsActive(false); } } //Store the Widget number keys from JSON to a string array - loadedWidgetsArray = (String[]) loadWidgetSettings.keys().toArray(new String[loadWidgetSettings.size()]); + String[] loadedWidgetsArray = (String[]) widgetContainerSettings.keys().toArray(new String[widgetContainerSettings.size()]); //printArray(loadedWidgetsArray); int widgetToActivate = 0; for (int w = 0; w < numLoadedWidgets; w++) { - String [] loadWidgetNameNumber = split(loadedWidgetsArray[w], '_'); - //Store the value of the widget to be activated - widgetToActivate = Integer.valueOf(loadWidgetNameNumber[1]); - //Load the container for the current widget[w] - int containerToApply = loadWidgetSettings.getInt(loadedWidgetsArray[w]); - - widgetManager.widgets.get(widgetToActivate).setIsActive(true);//activate the new widget - widgetManager.widgets.get(widgetToActivate).setContainer(containerToApply);//map it to the container that was loaded! - println("LoadGUISettings: Applied Widget " + widgetToActivate + " to Container " + containerToApply); - }//end case for all widget/container settings - - ///////////////////////////////////////////////////////////// - // Load more widget settings above this line as above // - ///////////////////////////////////////////////////////////// - - ///////////////////////////////////////////////////////////// - // Apply Settings below this line // - ///////////////////////////////////////////////////////////// + String [] loadWidgetNameNumber = split(loadedWidgetsArray[w], '_'); + //Store the value of the widget to be activated + widgetToActivate = Integer.valueOf(loadWidgetNameNumber[1]); + //Load the container for the current widget[w] + int containerToApply = widgetContainerSettings.getInt(loadedWidgetsArray[w]); - //Apply Data Smoothing for capable boards - if (currentBoard instanceof SmoothingCapableBoard) { - ((SmoothingCapableBoard)currentBoard).setSmoothingActive(loadDataSmoothingSetting); - topNav.updateSmoothingButtonText(); + widgetManager.widgets.get(widgetToActivate).setIsActive(true);//activate the new widget + widgetManager.widgets.get(widgetToActivate).setContainer(containerToApply);//map it to the container that was loaded! + println("SessionSettings: Applied Widget " + widgetToActivate + " to Container " + containerToApply); } + JSONObject widgetSettings = loadSettingsJSONData.getJSONObject(WIDGET_SETTINGS_KEY); + widgetManager.loadWidgetSettingsFromJson(widgetSettings.toString()); + //Load and apply all of the settings that are in dropdown menus. It's a bit much, so it has it's own function below. - loadApplyWidgetDropdownText(); + //loadApplyWidgetDropdownText(); //Apply Time Series Settings Last!!! - loadApplyTimeSeriesSettings(); - - //if (w_headPlot != null) { - //FIX ME - /* - //Force headplot to redraw if it is active - int hpWidgetNumber; - if (eegDataSource == DATASOURCE_GANGLION) { - hpWidgetNumber = 6; - } else { - hpWidgetNumber = 5; - } - if (widgetManager.widgets.get(hpWidgetNumber).getIsActive()) { - w_headPlot.headPlot.setPositionSize(w_headPlot.headPlot.hp_x, w_headPlot.headPlot.hp_y, w_headPlot.headPlot.hp_w, w_headPlot.headPlot.hp_h, w_headPlot.headPlot.hp_win_x, w_headPlot.headPlot.hp_win_y); - println("Headplot is active: Redrawing"); - } - */ - //} - } //end of loadGUISettings - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private void loadApplyWidgetDropdownText() { - - ////////Apply Time Series dropdown settings in loadApplyTimeSeriesSettings() instead of here - - ////////Apply FFT settings - //FIX ME - /* - MaxFreq(fftMaxFrqLoad); //This changes the back-end - w_fft.cp5_widget.getController("MaxFreq").getCaptionLabel().setText(fftMaxFrqArray[fftMaxFrqLoad]); //This changes front-end... etc. - - VertScale(fftMaxuVLoad); - w_fft.cp5_widget.getController("VertScale").getCaptionLabel().setText(fftVertScaleArray[fftMaxuVLoad]); - - LogLin(fftLogLinLoad); - w_fft.cp5_widget.getController("LogLin").getCaptionLabel().setText(fftLogLinArray[fftLogLinLoad]); - - Smoothing(fftSmoothingLoad); - w_fft.cp5_widget.getController("Smoothing").getCaptionLabel().setText(fftSmoothingArray[fftSmoothingLoad]); - - UnfiltFilt(fftFilterLoad); - w_fft.cp5_widget.getController("UnfiltFilt").getCaptionLabel().setText(fftFilterArray[fftFilterLoad]); - */ - - ////////Apply Accelerometer settings - //FIX ME - /* - if (w_accelerometer != null) { - accelVertScale(loadAccelVertScale); - w_accelerometer.cp5_widget.getController("accelVertScale").getCaptionLabel().setText(accVertScaleArray[loadAccelVertScale]); - - accelDuration(loadAccelHorizScale); - w_accelerometer.cp5_widget.getController("accelDuration").getCaptionLabel().setText(accHorizScaleArray[loadAccelHorizScale]); - } - */ - - ////////Apply Anolog Read dropdowns to Live Cyton Only - //FIX ME - /* - if (eegDataSource == DATASOURCE_CYTON) { - VertScale_AR(loadAnalogReadVertScale); - w_analogRead.cp5_widget.getController("VertScale_AR").getCaptionLabel().setText(arVertScaleArray[loadAnalogReadVertScale]); - - Duration_AR(loadAnalogReadHorizScale); - w_analogRead.cp5_widget.getController("Duration_AR").getCaptionLabel().setText(arHorizScaleArray[loadAnalogReadHorizScale]); - } - */ - - ////////////////////////////Apply Headplot settings - //FIX ME - /* - if (w_headPlot != null) { - Intensity(hpIntensityLoad); - w_headPlot.cp5_widget.getController("Intensity").getCaptionLabel().setText(hpIntensityArray[hpIntensityLoad]); - - Polarity(hpPolarityLoad); - w_headPlot.cp5_widget.getController("Polarity").getCaptionLabel().setText(hpPolarityArray[hpPolarityLoad]); - - ShowContours(hpContoursLoad); - w_headPlot.cp5_widget.getController("ShowContours").getCaptionLabel().setText(hpContoursArray[hpContoursLoad]); - - SmoothingHeadPlot(hpSmoothingLoad); - w_headPlot.cp5_widget.getController("SmoothingHeadPlot").getCaptionLabel().setText(hpSmoothingArray[hpSmoothingLoad]); - - //Force redraw headplot on load. Fixes issue where heaplot draws outside of the widget. - w_headPlot.headPlot.setPositionSize(w_headPlot.headPlot.hp_x, w_headPlot.headPlot.hp_y, w_headPlot.headPlot.hp_w, w_headPlot.headPlot.hp_h, w_headPlot.headPlot.hp_win_x, w_headPlot.headPlot.hp_win_y); - } - */ - - ////////////////////////////Apply Band Power settings - - //FIX ME - /* - try { - //apply channel checkbox settings - w_bandPower.bpChanSelect.deactivateAllButtons();; - for (int i = 0; i < loadBPActiveChans.size(); i++) { - w_bandPower.bpChanSelect.setToggleState(loadBPActiveChans.get(i), true); - } - } catch (Exception e) { - println("Settings: Exception caught applying band power settings " + e); - } - verbosePrint("Settings: Band Power Active Channels: " + loadBPActiveChans); - w_bandPower.setAutoClean(loadBPAutoClean); - w_bandPower.cp5_widget.getController("bpAutoCleanDropdown").getCaptionLabel().setText(w_bandPower.getAutoClean().getString()); - w_bandPower.setAutoCleanThreshold(loadBPAutoCleanThreshold); - w_bandPower.cp5_widget.getController("bpAutoCleanThresholdDropdown").getCaptionLabel().setText(w_bandPower.getAutoCleanThreshold().getString()); - w_bandPower.setAutoCleanTimer(loadBPAutoCleanTimer); - w_bandPower.cp5_widget.getController("bpAutoCleanTimerDropdown").getCaptionLabel().setText(w_bandPower.getAutoCleanTimer().getString()); - */ - - ////////////////////////////Apply Spectrogram settings - //Apply Max Freq dropdown - //FIX ME - /* - SpectrogramMaxFreq(spectMaxFrqLoad); - w_spectrogram.cp5_widget.getController("SpectrogramMaxFreq").getCaptionLabel().setText(spectMaxFrqArray[spectMaxFrqLoad]); - SpectrogramSampleRate(spectSampleRateLoad); - w_spectrogram.cp5_widget.getController("SpectrogramSampleRate").getCaptionLabel().setText(spectSampleRateArray[spectSampleRateLoad]); - SpectrogramLogLin(spectLogLinLoad); - */ - //FIX ME - //w_spectrogram.cp5_widget.getController("SpectrogramLogLin").getCaptionLabel().setText(fftLogLinArray[spectLogLinLoad]); - - /* - //FIX ME - try { - //apply channel checkbox settings - w_spectrogram.spectChanSelectTop.deactivateAllButtons(); - w_spectrogram.spectChanSelectBot.deactivateAllButtons(); - //close channel select when loading to prevent UI issues - w_spectrogram.spectChanSelectTop.setIsVisible(false); - w_spectrogram.spectChanSelectBot.setIsVisible(false); - for (int i = 0; i < loadSpectActiveChanTop.size(); i++) { - w_spectrogram.spectChanSelectTop.setToggleState(loadSpectActiveChanTop.get(i), true); - } - for (int i = 0; i < loadSpectActiveChanBot.size(); i++) { - w_spectrogram.spectChanSelectBot.setToggleState(loadSpectActiveChanBot.get(i), true); - } - w_spectrogram.screenResized(); - } catch (Exception e) { - println("Settings: Exception caught applying spectrogram settings channel bar " + e); - } - println("Settings: Spectrogram Active Channels: TOP - " + loadSpectActiveChanTop + " || BOT - " + loadSpectActiveChanBot); - */ - ///////////Apply Networking Settings - String nwSettingsString = loadNetworkingSettings.toString(); - dataProcessing.networkingSettings.loadJson(nwSettingsString); - - ////////////////////////////Apply EMG widget settings - try { - //apply channel checkbox settings - //FIX ME - /* - w_emg.emgChannelSelect.deactivateAllButtons();; - for (int i = 0; i < loadEmgActiveChannels.size(); i++) { - w_emg.emgChannelSelect.setToggleState(loadEmgActiveChannels.get(i), true); - } - */ - } catch (Exception e) { - println("Settings: Exception caught applying EMG widget settings " + e); - } - verbosePrint("Settings: EMG Widget Active Channels: " + loadEmgActiveChannels); - - ////////////////////////////Apply EMG Joystick settings - //FIX ME - /* - w_emgJoystick.setJoystickSmoothing(loadEmgJoystickSmoothing); - w_emgJoystick.cp5_widget.getController("emgJoystickSmoothingDropdown").getCaptionLabel() - .setText(EmgJoystickSmoothing.getEnumStringsAsList().get(loadEmgJoystickSmoothing)); - try { - for (int i = 0; i < loadEmgJoystickInputs.size(); i++) { - w_emgJoystick.updateJoystickInput(i, loadEmgJoystickInputs.get(i)); - } - } catch (Exception e) { - println("Settings: Exception caught applying EMG Joystick settings " + e); - } - */ - - //FIX ME - /* - ////////////////////////////Apply Marker Widget settings - w_marker.setMarkerWindow(loadMarkerWindow); - w_marker.cp5_widget.getController("markerWindowDropdown").getCaptionLabel().setText(w_marker.getMarkerWindow().getString()); - w_marker.setMarkerVertScale(loadMarkerVertScale); - w_marker.cp5_widget.getController("markerVertScaleDropdown").getCaptionLabel().setText(w_marker.getMarkerVertScale().getString()); - - ////////////////////////////Apply Focus Widget settings - w_focus.setMetric(loadFocusMetric); - w_focus.cp5_widget.getController("focusMetricDropdown").getCaptionLabel().setText(w_focus.getFocusMetric().getString()); - w_focus.setThreshold(loadFocusThreshold); - w_focus.cp5_widget.getController("focusThresholdDropdown").getCaptionLabel().setText(w_focus.getFocusThreshold().getString()); - w_focus.setFocusHorizScale(loadFocusWindow); - w_focus.cp5_widget.getController("focusWindowDropdown").getCaptionLabel().setText(w_focus.getFocusWindow().getString()); - */ - //////////////////////////////////////////////////////////// - // Apply more loaded widget settings above this line // - - } //end of loadApplyWidgetDropdownText() - - private void loadApplyTimeSeriesSettings() { - - JSONObject loadTimeSeriesSettings = loadSettingsJSONData.getJSONObject(kJSONKeyTimeSeries); - ////////Apply Time Series widget settings - //FIX ME - /* - w_timeSeries.setVerticalScale(loadTimeSeriesSettings.getInt("Time Series Vert Scale")); - w_timeSeries.cp5_widget.getController("VertScale_TS").getCaptionLabel().setText(w_timeSeries.getVerticalScale().getString()); //changes front-end - - w_timeSeries.setHorizontalScale(loadTimeSeriesSettings.getInt("Time Series Horiz Scale")); - w_timeSeries.cp5_widget.getController("Duration").getCaptionLabel().setText(w_timeSeries.getHorizontalScale().getString()); - - w_timeSeries.setLabelMode(loadTimeSeriesSettings.getInt("Time Series Label Mode")); - w_timeSeries.cp5_widget.getController("LabelMode_TS").getCaptionLabel().setText(w_timeSeries.getLabelMode().getString()); - - JSONArray loadTSChan = loadTimeSeriesSettings.getJSONArray("activeChannels"); - w_timeSeries.tsChanSelect.deactivateAllButtons(); - try { - for (int i = 0; i < loadTSChan.size(); i++) { - w_timeSeries.tsChanSelect.setToggleState(loadTSChan.getInt(i), true); - } - } catch (Exception e) { - println("Settings: Exception caught applying time series settings " + e); - } - verbosePrint("Settings: Time Series Active Channels: " + loadBPActiveChans); - */ - } //end loadApplyTimeSeriesSettings + //loadApplyTimeSeriesSettings(); + } /** * @description Used in TopNav when user clicks ClearSettings->AreYouSure->Yes @@ -926,9 +273,9 @@ class SessionSettings { String filePath = directoryManager.getSettingsPath(); String[] fileNames = new String[7]; if (_mode.equals("Default")) { - fileNames = defaultSettingsFiles; + fileNames = DEFAULT_SETTINGS_FILES; } else if (_mode.equals("User")) { - fileNames = userSettingsFiles; + fileNames = USER_SETTINGS_FILES; } else { filePath = "Error"; } @@ -1046,7 +393,7 @@ class SessionSettings { loadKeyPressed(); } -} //end of Software Settings class +} ////////////////////////////////////////// // Global Functions // diff --git a/OpenBCI_GUI/W_PacketLoss.pde b/OpenBCI_GUI/W_PacketLoss.pde index fbcc6bea6..f8d8be27d 100644 --- a/OpenBCI_GUI/W_PacketLoss.pde +++ b/OpenBCI_GUI/W_PacketLoss.pde @@ -64,7 +64,7 @@ class W_PacketLoss extends Widget { tableDropdown = cp5_widget.addScrollableList("TableTimeWindow") .setDrawOutline(false) .setOpen(false) - .setColor(sessionSettings.dropdownColors) + .setColor(dropdownColorsGlobal) .setOutlineColor(OBJECT_BORDER_GREY) .setBarHeight(CELL_HEIGHT) //height of top/primary bar .setItemHeight(CELL_HEIGHT) //height of all item/dropdown bars diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index 491c8148e..f0d5e5c5a 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -465,4 +465,70 @@ public void spectrogramWindowDropdown(int n) { public void spectrogramLogLinDropdown(int n) { ((W_Spectrogram) widgetManager.getWidget("W_Spectrogram")).setLogLin(n); -} \ No newline at end of file +} + + +//Save data from the Active channel checkBoxes - Top +//JSONArray saveActiveChanSpectTop = new JSONArray(); +/* +//FIX ME +int numActiveSpectChanTop = w_spectrogram.spectChanSelectTop.getActiveChannels().size(); +for (int i = 0; i < numActiveSpectChanTop; i++) { + int activeChannel = w_spectrogram.spectChanSelectTop.getActiveChannels().get(i); + saveActiveChanSpectTop.setInt(i, activeChannel); +} +saveSpectrogramSettings.setJSONArray("activeChannelsTop", saveActiveChanSpectTop); +//Save data from the Active channel checkBoxes - Bottom +JSONArray saveActiveChanSpectBot = new JSONArray(); +int numActiveSpectChanBot = w_spectrogram.spectChanSelectBot.getActiveChannels().size(); +for (int i = 0; i < numActiveSpectChanBot; i++) { + int activeChannel = w_spectrogram.spectChanSelectBot.getActiveChannels().get(i); + saveActiveChanSpectBot.setInt(i, activeChannel); +} +*/ +//saveSpectrogramSettings.setJSONArray("activeChannelsBot", saveActiveChanSpectBot); +//Save Spectrogram_Max Freq Setting. The max frq variable is updated every time the user selects a dropdown in the spectrogram widget +//FIX ME +/* +saveSpectrogramSettings.setInt("Spectrogram_Max Freq", spectMaxFrqSave); +saveSpectrogramSettings.setInt("Spectrogram_Sample Rate", spectSampleRateSave); +saveSpectrogramSettings.setInt("Spectrogram_LogLin", spectLogLinSave); +*/ + +//Get Band Power widget settings + //FIX ME + /* + loadBPActiveChans.clear(); + JSONObject loadBPSettings = loadSettingsJSONData.getJSONObject(kJSONKeyBandPower); + JSONArray loadBPChan = loadBPSettings.getJSONArray("activeChannels"); + for (int i = 0; i < loadBPChan.size(); i++) { + loadBPActiveChans.add(loadBPChan.getInt(i)); + } + loadBPAutoClean = loadBPSettings.getInt("bpAutoClean"); + loadBPAutoCleanThreshold = loadBPSettings.getInt("bpAutoCleanThreshold"); + loadBPAutoCleanTimer = loadBPSettings.getInt("bpAutoCleanTimer"); + //println("Settings: band power active chans loaded = " + loadBPActiveChans ); + */ + + /* + try { + //Get Spectrogram widget settings + loadSpectActiveChanTop.clear(); + loadSpectActiveChanBot.clear(); + JSONObject loadSpectSettings = loadSettingsJSONData.getJSONObject(kJSONKeySpectrogram); + JSONArray loadSpectChanTop = loadSpectSettings.getJSONArray("activeChannelsTop"); + for (int i = 0; i < loadSpectChanTop.size(); i++) { + loadSpectActiveChanTop.add(loadSpectChanTop.getInt(i)); + } + JSONArray loadSpectChanBot = loadSpectSettings.getJSONArray("activeChannelsBot"); + for (int i = 0; i < loadSpectChanBot.size(); i++) { + loadSpectActiveChanBot.add(loadSpectChanBot.getInt(i)); + } + spectMaxFrqLoad = loadSpectSettings.getInt("Spectrogram_Max Freq"); + spectSampleRateLoad = loadSpectSettings.getInt("Spectrogram_Sample Rate"); + spectLogLinLoad = loadSpectSettings.getInt("Spectrogram_LogLin"); + //println(loadSpectActiveChanTop, loadSpectActiveChanBot); + } catch (Exception e) { + e.printStackTrace(); + } +*/ \ No newline at end of file diff --git a/OpenBCI_GUI/W_Template.pde b/OpenBCI_GUI/W_Template.pde index cb42436e9..ea7a2c4c1 100644 --- a/OpenBCI_GUI/W_Template.pde +++ b/OpenBCI_GUI/W_Template.pde @@ -162,3 +162,18 @@ class W_Template extends WidgetWithSettings { println("Item " + (n+1) + " selected from Dropdown 3"); } }; + + +public void widgetTemplateDropdown1(int n) { + // This is the callback function for the first dropdown. It will be called when the user selects an item from the dropdown. + // The parameter "n" is the index of the selected item. + ((W_Template) widgetManager.getWidget("W_Template")).setDropdown1(n); +} + +public void widgetTemplateDropdown2(int n) { + ((W_Template) widgetManager.getWidget("W_Template")).setDropdown2(n); +} + +public void widgetTemplateDropdown3(int n) { + ((W_Template) widgetManager.getWidget("W_Template")).setDropdown3(n); +} \ No newline at end of file diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index 925225c90..485b53c3f 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -81,12 +81,12 @@ class Widget { } public void setupWidgetSelectorDropdown(ArrayList _widgetOptions){ - cp5_widget.setColor(sessionSettings.dropdownColors); + cp5_widget.setColor(dropdownColorsGlobal); ScrollableList scrollList = cp5_widget.addScrollableList("WidgetSelector") .setPosition(x0+2, y0+2) //upper left corner // .setFont(h2) .setOpen(false) - .setColor(sessionSettings.dropdownColors) + .setColor(dropdownColorsGlobal) .setOutlineColor(OBJECT_BORDER_GREY) //.setSize(widgetSelectorWidth, int(h0 * widgetDropdownScaling) )// + maxFreqList.size()) //.setSize(widgetSelectorWidth, (NUM_WIDGETS_TO_SHOW+1)*(navH-4) )// + maxFreqList.size()) @@ -116,7 +116,7 @@ class Widget { } public void setupNavDropdowns(){ - cp5_widget.setColor(sessionSettings.dropdownColors); + cp5_widget.setColor(dropdownColorsGlobal); // println("Setting up dropdowns..."); for(int i = 0; i < dropdowns.size(); i++){ int dropdownPos = dropdowns.size() - i; @@ -125,7 +125,7 @@ class Widget { .setPosition(x0+w0-(dropdownWidth*(dropdownPos))-(2*(dropdownPos)), y0 + navH + 2) //float right .setFont(h5) .setOpen(false) - .setColor(sessionSettings.dropdownColors) + .setColor(dropdownColorsGlobal) .setOutlineColor(OBJECT_BORDER_GREY) .setSize(dropdownWidth, (dropdowns.get(i).items.size()+1)*(navH-4) )// + maxFreqList.size()) .setBarHeight(navH-4) @@ -324,13 +324,13 @@ abstract class WidgetWithSettings extends Widget { } public WidgetSettings getWidgetSettings() { - // FIX ME - DO I NEED THIS? return widgetSettings; } protected void initWidgetSettings() { widgetSettings = new WidgetSettings(getWidgetTitle()); } + protected abstract void applySettings(); } diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index edc02ea99..9ccc18b2f 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -315,4 +315,43 @@ class WidgetManager { public int getWidgetCount() { return widgets.size(); } + + public String getWidgetSettingsAsJson() { + StringBuilder allWidgetSettings = new StringBuilder(); + allWidgetSettings.append("{"); + for (Widget widget : widgets) { + if (!(widget instanceof WidgetWithSettings)) { + continue; + } + println("Found widget with settings: " + widget.getWidgetTitle()); + WidgetSettings widgetSettings = ((WidgetWithSettings) widget).getWidgetSettings(); + String json = widgetSettings.toJSON(); + allWidgetSettings.append("\"").append(widget.getWidgetTitle()).append("\": "); + allWidgetSettings.append(json).append(", "); + } + allWidgetSettings.append("}"); + return allWidgetSettings.toString(); + } + + public void loadWidgetSettingsFromJson(String widgetSettingsJson) { + JSONObject json = parseJSONObject(widgetSettingsJson); + if (json == null) { + println("WidgetManager:loadWidgetSettingsFromJson: Failed to parse JSON string."); + return; + } + + for (Widget widget : widgets) { + if (!(widget instanceof WidgetWithSettings)) { + continue; + } + + String widgetTitle = widget.getWidgetTitle(); + if (json.hasKey(widgetTitle)) { + String settingsJson = json.getString(widgetTitle, ""); + ((WidgetWithSettings) widget).getWidgetSettings().fromJSON(settingsJson); + } else { + println("WidgetManager:loadWidgetSettingsFromJson: No settings found for " + widgetTitle); + } + } + } }; \ No newline at end of file diff --git a/OpenBCI_GUI/WidgetSettings.pde b/OpenBCI_GUI/WidgetSettings.pde index b284b11ac..bfdd967bc 100644 --- a/OpenBCI_GUI/WidgetSettings.pde +++ b/OpenBCI_GUI/WidgetSettings.pde @@ -1,35 +1,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; -//Used for Widget Dropdown Enums -interface IndexingInterface { - public int getIndex(); - public String getString(); -} - -/** - * Helper class for working with IndexingInterface enums - */ -public static class EnumHelper { - /** - * Generic method to get enum strings as a list - */ - public static List getListAsStrings(T[] values) { - List enumStrings = new ArrayList<>(); - for (T enumValue : values) { - enumStrings.add(enumValue.getString()); - } - return enumStrings; - } - - /** - * Get list of strings for an enum class that implements IndexingInterface - */ - public static & IndexingInterface> List getEnumStrings(Class enumClass) { - return getListAsStrings(enumClass.getEnumConstants()); - } -} - /** * Simple storage for widget settings that converts to/from JSON */ @@ -123,7 +94,7 @@ class WidgetSettings { */ public String toJSON() { JSONObject json = new JSONObject(); - json.setString("widget", widgetName); + json.setString("widgetTitle", widgetName); JSONArray items = new JSONArray(); int i = 0; @@ -148,7 +119,7 @@ class WidgetSettings { JSONObject json = parseJSONObject(jsonString); if (json == null) return false; - String loadedWidget = json.getString("widget", ""); + String loadedWidget = json.getString("widgetTitle", ""); if (!loadedWidget.equals(widgetName)) { println("Warning: Widget mismatch. Expected: " + widgetName + ", Found: " + loadedWidget); } From 073205de5dbecbe1cc628d851827f105805240ae Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 17 Apr 2025 13:17:12 -0500 Subject: [PATCH 15/29] Remove mention of Ganglion in [ and ] keys in Interactivity.pde --- OpenBCI_GUI/Interactivity.pde | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenBCI_GUI/Interactivity.pde b/OpenBCI_GUI/Interactivity.pde index 3307b4b99..eba496f1c 100644 --- a/OpenBCI_GUI/Interactivity.pde +++ b/OpenBCI_GUI/Interactivity.pde @@ -213,12 +213,12 @@ void parseKey(char val) { // Fixes #976. These keyboard shortcuts enable synthetic square waves on Ganglion and Cyton if (currentBoard instanceof BoardGanglion || currentBoard instanceof BoardCyton) { if (val == '[' || val == ']') { - println("Expert Mode: '" + val + "' pressed. Sending to Ganglion..."); + println("Expert Mode: '" + val + "' pressed. Sending to board..."); Boolean success = ((Board)currentBoard).sendCommand(str(val)).getKey(); if (success) { - outputSuccess("Expert Mode: Success sending '" + val + "' to Ganglion!"); + outputSuccess("Expert Mode: Success sending '" + val + "' to board!"); } else { - outputWarn("Expert Mode: Error sending '" + val + "' to Ganglion. Try again with data stream stopped."); + outputWarn("Expert Mode: Error sending '" + val + "' to board. Try again with data stream stopped."); } return; } From 074fff7f355ff697489f52b0c36c6cc7b9c68fd4 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 17 Apr 2025 19:52:49 -0500 Subject: [PATCH 16/29] Add functional save/load widget settings with simpler method calls - Test driven with W_Template --- OpenBCI_GUI/W_Template.pde | 32 ++++++++++-------------------- OpenBCI_GUI/Widget.pde | 13 +++++++++++- OpenBCI_GUI/WidgetManager.pde | 16 +++++++++++---- OpenBCI_GUI/WidgetSettings.pde | 32 +++++++++++++++++++----------- OpenBCI_GUI/WidgetWithSettings.pde | 0 5 files changed, 55 insertions(+), 38 deletions(-) delete mode 100644 OpenBCI_GUI/WidgetWithSettings.pde diff --git a/OpenBCI_GUI/W_Template.pde b/OpenBCI_GUI/W_Template.pde index ea7a2c4c1..78dd3a64f 100644 --- a/OpenBCI_GUI/W_Template.pde +++ b/OpenBCI_GUI/W_Template.pde @@ -40,7 +40,6 @@ class W_Template extends WidgetWithSettings { localCP5.setAutoDraw(false); createWidgetTemplateButton(); - } @Override @@ -48,34 +47,25 @@ class W_Template extends WidgetWithSettings { super.initWidgetSettings(); // Widget Settings are used to store the state of the widget. // This is where you can set the default values for your dropdowns and other settings. - widgetSettings.set(TemplateDropdown1.class, TemplateDropdown1.ITEM_A); - widgetSettings.set(TemplateDropdown2.class, TemplateDropdown2.ITEM_C); - widgetSettings.set(TemplateDropdown3.class, TemplateDropdown3.ITEM_F); - widgetSettings.saveDefaults(); - - // Get the list of Strings for the dropdowns. These are the options that will be displayed in the dropdowns. - List dropdown1List = EnumHelper.getEnumStrings(TemplateDropdown1.class); - List dropdown2List = EnumHelper.getEnumStrings(TemplateDropdown2.class); - List dropdown3List = EnumHelper.getEnumStrings(TemplateDropdown3.class); - - // Get the current settings for the dropdowns. This is where you can retrieve the values that were just set. - int dropdown1Index = widgetSettings.get(TemplateDropdown1.class).getIndex(); - int dropdown2Index = widgetSettings.get(TemplateDropdown2.class).getIndex(); - int dropdown3Index = widgetSettings.get(TemplateDropdown3.class).getIndex(); + widgetSettings.set(TemplateDropdown1.class, TemplateDropdown1.ITEM_A) + .set(TemplateDropdown2.class, TemplateDropdown2.ITEM_C) + .set(TemplateDropdown3.class, TemplateDropdown3.ITEM_F) + .saveDefaults(); // This is the protocol for setting up dropdowns. // Note that these 3 dropdowns correspond to the 3 global functions below. // You just need to make sure the "id" (the 1st String) has the same name as the corresponding function. - addDropdown("widgetTemplateDropdown1", "Drop 1", dropdown1List, dropdown1Index); - addDropdown("widgetTemplateDropdown2", "Drop 2", dropdown2List, dropdown2Index); - addDropdown("widgetTemplateDropdown3", "Drop 3", dropdown3List, dropdown3Index); + // Arguments: Class, String id, String label + initDropdown(TemplateDropdown1.class, "widgetTemplateDropdown1", "Drop 1"); + initDropdown(TemplateDropdown2.class, "widgetTemplateDropdown2", "Drop 2"); + initDropdown(TemplateDropdown3.class, "widgetTemplateDropdown3", "Drop 3"); } @Override protected void applySettings() { - //FIX ME - println("!!!!!!!!!!!!!!!!!!!!!!!!!Applying settings for " + widgetTitle); - //widgetSettings.applySettings(); + updateDropdownLabel(TemplateDropdown1.class, "widgetTemplateDropdown1"); + updateDropdownLabel(TemplateDropdown2.class, "widgetTemplateDropdown2"); + updateDropdownLabel(TemplateDropdown3.class, "widgetTemplateDropdown3"); } public void update(){ diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index 485b53c3f..723f3d48d 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -319,8 +319,8 @@ abstract class WidgetWithSettings extends Widget { } public void setWidgetSettings(WidgetSettings _widgetSettings) { - //FIX ME - DO I NEED THIS? widgetSettings = _widgetSettings; + applySettings(); } public WidgetSettings getWidgetSettings() { @@ -330,6 +330,17 @@ abstract class WidgetWithSettings extends Widget { protected void initWidgetSettings() { widgetSettings = new WidgetSettings(getWidgetTitle()); } + + protected & IndexingInterface> void initDropdown(Class enumClass, String id, String label) { + List options = EnumHelper.getEnumStrings(enumClass); + int currentIndex = widgetSettings.get(enumClass).getIndex(); + addDropdown(id, label, options, currentIndex); + } + + protected & IndexingInterface> void updateDropdownLabel(Class enumClass, String controllerId) { + String value = widgetSettings.get(enumClass).getString(); + cp5_widget.getController(controllerId).getCaptionLabel().setText(value); + } protected abstract void applySettings(); } diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index 9ccc18b2f..4a688e54b 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -345,13 +345,21 @@ class WidgetManager { continue; } + WidgetWithSettings widgetWithSettings = (WidgetWithSettings) widget; String widgetTitle = widget.getWidgetTitle(); - if (json.hasKey(widgetTitle)) { - String settingsJson = json.getString(widgetTitle, ""); - ((WidgetWithSettings) widget).getWidgetSettings().fromJSON(settingsJson); - } else { + if (!json.hasKey(widgetTitle)) { println("WidgetManager:loadWidgetSettingsFromJson: No settings found for " + widgetTitle); + continue; + } + + String settingsJson = json.getString(widgetTitle, ""); + WidgetSettings widgetSettings = widgetWithSettings.getWidgetSettings(); + boolean success = widgetSettings.loadFromJSON(settingsJson); + if (!success) { + println("WidgetManager:loadWidgetSettingsFromJson: Failed to load settings for " + widgetTitle); + continue; } + widgetWithSettings.applySettings(); } } }; \ No newline at end of file diff --git a/OpenBCI_GUI/WidgetSettings.pde b/OpenBCI_GUI/WidgetSettings.pde index bfdd967bc..ad3449e20 100644 --- a/OpenBCI_GUI/WidgetSettings.pde +++ b/OpenBCI_GUI/WidgetSettings.pde @@ -17,9 +17,11 @@ class WidgetSettings { /** * Store a setting using enum class as key + * @return this WidgetSettings instance for method chaining */ - public > void set(Class enumClass, T value) { + public > WidgetSettings set(Class enumClass, T value) { settings.put(enumClass.getName(), value); + return this; } /** @@ -28,9 +30,9 @@ class WidgetSettings { * * @param enumClass The enum class to look up values * @param index The index of the enum constant to set - * @return true if successful, false if the index is out of bounds + * @return this WidgetSettings instance for method chaining */ - public > boolean setByIndex(Class enumClass, int index) { + public > WidgetSettings setByIndex(Class enumClass, int index) { T[] enumConstants = enumClass.getEnumConstants(); // Check if index is valid @@ -39,12 +41,12 @@ class WidgetSettings { T value = enumConstants[index]; // Set it using the regular set method set(enumClass, value); - return true; + } else { + // Index was out of bounds + println("Warning: Invalid index " + index + " for enum " + enumClass.getName()); } - // Index was out of bounds - println("Warning: Invalid index " + index + " for enum " + enumClass.getName()); - return false; + return this; } /** @@ -77,16 +79,20 @@ class WidgetSettings { /** * Save current settings as defaults + * @return this WidgetSettings instance for method chaining */ - public void saveDefaults() { + public WidgetSettings saveDefaults() { defaults = new HashMap>(settings); + return this; } /** * Restore to default settings + * @return this WidgetSettings instance for method chaining */ - public void restoreDefaults() { + public WidgetSettings restoreDefaults() { settings = new HashMap>(defaults); + return this; } /** @@ -112,9 +118,11 @@ class WidgetSettings { } /** - * Load settings from JSON string - */ - public boolean fromJSON(String jsonString) { + * Attempts to load settings from a JSON string + * @param jsonString The JSON string containing settings + * @return true if settings were loaded successfully, false otherwise + */ + public boolean loadFromJSON(String jsonString) { try { JSONObject json = parseJSONObject(jsonString); if (json == null) return false; diff --git a/OpenBCI_GUI/WidgetWithSettings.pde b/OpenBCI_GUI/WidgetWithSettings.pde deleted file mode 100644 index e69de29bb..000000000 From 93757b33bcac53e929073ad07ab4c705a2775a1a Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Fri, 18 Apr 2025 16:34:34 -0500 Subject: [PATCH 17/29] Add ability to save ChannelSelect settings to WidgetWithSettings class and apply this to TimeSeries Widget --- OpenBCI_GUI/ChannelSelect.pde | 11 + OpenBCI_GUI/TimeSeriesWidgetHelperClasses.pde | 718 +++++++++++++++ OpenBCI_GUI/W_TimeSeries.pde | 841 ++---------------- OpenBCI_GUI/Widget.pde | 128 ++- OpenBCI_GUI/WidgetManager.pde | 27 +- OpenBCI_GUI/WidgetSettings.pde | 463 ++++++++-- 6 files changed, 1307 insertions(+), 881 deletions(-) create mode 100644 OpenBCI_GUI/TimeSeriesWidgetHelperClasses.pde diff --git a/OpenBCI_GUI/ChannelSelect.pde b/OpenBCI_GUI/ChannelSelect.pde index 4b35e4764..963101cfd 100644 --- a/OpenBCI_GUI/ChannelSelect.pde +++ b/OpenBCI_GUI/ChannelSelect.pde @@ -232,6 +232,17 @@ class ExGChannelSelect extends ChannelSelect { return activeChannels; } + public void updateChannelSelection(List channels) { + // First deactivate all channels + deactivateAllButtons(); + + // Then activate only the selected channels + for (Integer channel : channels) { + if (channel >= 0 && channel < channelButtons.size()) { + setToggleState(channel, true); // Changed from toggleButton + } + } + } } class DualChannelSelector { diff --git a/OpenBCI_GUI/TimeSeriesWidgetHelperClasses.pde b/OpenBCI_GUI/TimeSeriesWidgetHelperClasses.pde new file mode 100644 index 000000000..994cba5c4 --- /dev/null +++ b/OpenBCI_GUI/TimeSeriesWidgetHelperClasses.pde @@ -0,0 +1,718 @@ + +//======================================================================================================================== +// CHANNEL BAR CLASS -- Implemented by Time Series Widget Class +//======================================================================================================================== +//this class contains the plot and buttons for a single channel of the Time Series widget +//one of these will be created for each channel (4, 8, or 16) +class ChannelBar { + + int channelIndex; //duh + String channelString; + int x, y, w, h; + int defaultH; + ControlP5 cbCp5; + Button onOffButton; + int onOff_diameter; + int yScaleButton_h; + int yScaleButton_w; + Button yScaleButton_pos; + Button yScaleButton_neg; + int yAxisLabel_h; + private TextBox yAxisMax; + private TextBox yAxisMin; + + int yAxisUpperLim; + int yAxisLowerLim; + int uiSpaceWidth; + int padding_4 = 4; + int minimumChannelHeight; + int plotBottomWellH = 35; + + GPlot plot; //the actual grafica-based GPlot that will be rendering the Time Se ries trace + GPointsArray channelPoints; + int nPoints; + int numSeconds; + float timeBetweenPoints; + private GPlotAutoscaler gplotAutoscaler; + + color channelColor; //color of plot trace + + TextBox voltageValue; + TextBox impValue; + + boolean drawVoltageValue; + + ChannelBar(PApplet _parentApplet, int _channelIndex, int _x, int _y, int _w, int _h, PImage expand_default, PImage expand_hover, PImage expand_active, PImage contract_default, PImage contract_hover, PImage contract_active) { + + cbCp5 = new ControlP5(ourApplet); + cbCp5.setGraphics(ourApplet, x, y); + cbCp5.setAutoDraw(false); //Setting this saves code as cp5 elements will only be drawn/visible when [cp5].draw() is called + + channelIndex = _channelIndex; + channelString = str(channelIndex + 1); + + x = _x; + y = _y; + w = _w; + h = _h; + defaultH = h; + + onOff_diameter = h > 26 ? 26 : h - 2; + createOnOffButton("onOffButton"+channelIndex, channelString, x + 6, y + int(h/2) - int(onOff_diameter/2), onOff_diameter, onOff_diameter); + + //Create GPlot for this Channel + uiSpaceWidth = 36 + padding_4; + yAxisUpperLim = 200; + yAxisLowerLim = -200; + numSeconds = 5; + plot = new GPlot(_parentApplet); + plot.setPos(x + uiSpaceWidth, y); + plot.setDim(w - uiSpaceWidth, h); + plot.setMar(0f, 0f, 0f, 0f); + plot.setLineColor((int)channelColors[channelIndex%8]); + plot.setXLim(-5,0); + plot.setYLim(yAxisLowerLim, yAxisUpperLim); + plot.setPointSize(2); + plot.setPointColor(0); + plot.setAllFontProperties("Arial", 0, 14); + plot.getXAxis().setFontColor(OPENBCI_DARKBLUE); + plot.getXAxis().setLineColor(OPENBCI_DARKBLUE); + plot.getXAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); + if (channelIndex == globalChannelCount-1) { + plot.getXAxis().setAxisLabelText("Time (s)"); + plot.getXAxis().getAxisLabel().setOffset(plotBottomWellH/2 + 5f); + } + gplotAutoscaler = new GPlotAutoscaler(); + + //Fill the GPlot with initial data + nPoints = nPointsBasedOnDataSource(); + channelPoints = new GPointsArray(nPoints); + timeBetweenPoints = (float)numSeconds / (float)nPoints; + for (int i = 0; i < nPoints; i++) { + float time = -(float)numSeconds + (float)i*timeBetweenPoints; + float filt_uV_value = 0.0; //0.0 for all points to start + GPoint tempPoint = new GPoint(time, filt_uV_value); + channelPoints.set(i, tempPoint); + } + plot.setPoints(channelPoints); //set the plot with 0.0 for all channelPoints to start + + //Create a UI to custom scale the Y axis for this channel + yScaleButton_w = 18; + yScaleButton_h = 18; + yAxisLabel_h = 12; + int padding = 2; + yAxisMax = new TextBox("+"+yAxisUpperLim+"uV", x + uiSpaceWidth + padding, y + int(padding*1.5), OPENBCI_DARKBLUE, color(255,255,255,175), LEFT, TOP); + yAxisMin = new TextBox(yAxisLowerLim+"uV", x + uiSpaceWidth + padding, y + h - yAxisLabel_h - padding_4, OPENBCI_DARKBLUE, color(255,255,255,175), LEFT, TOP); + customYLim(yAxisMax, yAxisUpperLim); + customYLim(yAxisMin, yAxisLowerLim); + yScaleButton_neg = createYScaleButton(channelIndex, false, "decreaseYscale", "-T", x + uiSpaceWidth + padding, y + w/2 - yScaleButton_h/2, yScaleButton_w, yScaleButton_h, contract_default, contract_hover, contract_active); + yScaleButton_pos = createYScaleButton(channelIndex, true, "increaseYscale", "+T", x + uiSpaceWidth + padding*2 + yScaleButton_w, y + w/2 - yScaleButton_h/2, yScaleButton_w, yScaleButton_h, expand_default, expand_hover, expand_active); + + //Create textBoxes to display the current values + impValue = new TextBox("", x + uiSpaceWidth + (int)plot.getDim()[0], y + padding, OPENBCI_DARKBLUE, color(255,255,255,175), RIGHT, TOP); + voltageValue = new TextBox("", x + uiSpaceWidth + (int)plot.getDim()[0] - padding, y + h, OPENBCI_DARKBLUE, color(255,255,255,175), RIGHT, BOTTOM); + drawVoltageValue = true; + + //Establish a minimumChannelHeight + minimumChannelHeight = padding_4 + yAxisLabel_h*2; + } + + void update(boolean hardwareSettingsAreOpen, TimeSeriesLabelMode _labelMode) { + + //Reusable variables + String fmt; float val; + + //Update the voltage value TextBox + val = dataProcessing.data_std_uV[channelIndex]; + voltageValue.string = String.format(getFmt(val),val) + " uVrms"; + if (is_railed != null) { + voltageValue.setText(is_railed[channelIndex].notificationString + voltageValue.string); + voltageValue.setTextColor(OPENBCI_DARKBLUE); + color bgColor = color(255,255,255,175); // Default white background for voltage TextBox + if (is_railed[channelIndex].is_railed) { + bgColor = SIGNAL_CHECK_RED_LOWALPHA; + } else if (is_railed[channelIndex].is_railed_warn) { + bgColor = SIGNAL_CHECK_YELLOW_LOWALPHA; + } + voltageValue.setBackgroundColor(bgColor); + } + + //update the impedance values + val = data_elec_imp_ohm[channelIndex]/1000; + fmt = String.format(getFmt(val),val) + " kOhm"; + if (is_railed != null && is_railed[channelIndex].is_railed == true) { + fmt = "RAILED - " + fmt; + } + impValue.setText(fmt); + + // update data in plot + updatePlotPoints(); + + if (currentBoard.isEXGChannelActive(channelIndex)) { + onOffButton.setColorBackground(channelColors[channelIndex%8]); // power down == false, set color to vibrant + } + else { + onOffButton.setColorBackground(50); // power down == true, set to grey + } + + //Hide yAxisButtons when hardware settings are open, using autoscale, and labels are turn on + boolean b = !hardwareSettingsAreOpen + && h > minimumChannelHeight + && !gplotAutoscaler.getEnabled() + && _labelMode == TimeSeriesLabelMode.ON; + yScaleButton_pos.setVisible(b); + yScaleButton_neg.setVisible(b); + yScaleButton_pos.setUpdate(b); + yScaleButton_neg.setUpdate(b); + b = !hardwareSettingsAreOpen + && h > minimumChannelHeight + && _labelMode == TimeSeriesLabelMode.ON; + yAxisMin.setVisible(b); + yAxisMax.setVisible(b); + voltageValue.setVisible(_labelMode != TimeSeriesLabelMode.OFF); + } + + private String getFmt(float val) { + String fmt; + if (val > 100.0f) { + fmt = "%.0f"; + } else if (val > 10.0f) { + fmt = "%.1f"; + } else { + fmt = "%.2f"; + } + return fmt; + } + + private void updatePlotPoints() { + float[][] buffer = downsampledFilteredBuffer.getBuffer(); + final int bufferSize = buffer[channelIndex].length; + final int startIndex = bufferSize - nPoints; + for (int i = startIndex; i < bufferSize; i++) { + int adjustedIndex = i - startIndex; + float time = -(float)numSeconds + (float)(adjustedIndex)*timeBetweenPoints; + float filt_uV_value = buffer[channelIndex][i]; + channelPoints.set(adjustedIndex, time, filt_uV_value, ""); + } + plot.setPoints(channelPoints); + + gplotAutoscaler.update(plot, channelPoints); + + if (gplotAutoscaler.getEnabled()) { + float[] minMax = gplotAutoscaler.getMinMax(); + customYLim(yAxisMin, (int)minMax[0]); + customYLim(yAxisMax, (int)minMax[1]); + } + } + + public void draw(boolean hardwareSettingsAreOpen) { + + plot.beginDraw(); + plot.drawBox(); + plot.drawGridLines(GPlot.VERTICAL); + try { + plot.drawLines(); + } catch (NullPointerException e) { + e.printStackTrace(); + println("PLOT ERROR ON CHANNEL " + channelIndex); + + } + //Draw the x axis label on the bottom channel bar, hide if hardware settings are open + if (isBottomChannel() && !hardwareSettingsAreOpen) { + plot.drawXAxis(); + plot.getXAxis().draw(); + } + plot.endDraw(); + + //draw channel holder background + pushStyle(); + stroke(OPENBCI_BLUE_ALPHA50); + noFill(); + rect(x,y,w,h); + popStyle(); + + //draw channelBar separator line in the middle of INTER_CHANNEL_BAR_SPACE + if (!isBottomChannel()) { + pushStyle(); + stroke(OPENBCI_DARKBLUE); + strokeWeight(1); + int separator_y = y + h + int(widgetManager.getTimeSeriesWidget().INTER_CHANNEL_BAR_SPACE / 2); + line(x, separator_y, x + w, separator_y); + popStyle(); + } + + //draw impedance values in time series also for each channel + drawVoltageValue = true; + if (currentBoard instanceof ImpedanceSettingsBoard) { + if (((ImpedanceSettingsBoard)currentBoard).isCheckingImpedance(channelIndex)) { + impValue.draw(); + drawVoltageValue = false; + } + } + + if (drawVoltageValue) { + voltageValue.draw(); + } + + try { + cbCp5.draw(); + } catch (NullPointerException e) { + e.printStackTrace(); + println("CP5 ERROR ON CHANNEL " + channelIndex); + } + + yAxisMin.draw(); + yAxisMax.draw(); + } + + private int nPointsBasedOnDataSource() { + return (numSeconds * currentBoard.getSampleRate()) / getDownsamplingFactor(); + } + + public void adjustTimeAxis(int _newTimeSize) { + numSeconds = _newTimeSize; + plot.setXLim(-_newTimeSize,0); + + nPoints = nPointsBasedOnDataSource(); + channelPoints = new GPointsArray(nPoints); + timeBetweenPoints = (float)numSeconds / (float)nPoints; + if (_newTimeSize > 1) { + plot.getXAxis().setNTicks(_newTimeSize); //sets the number of axis divisions... + }else{ + plot.getXAxis().setNTicks(10); + } + + updatePlotPoints(); + } + + public void adjustVertScale(int _vertScaleValue) { + boolean enableAutoscale = _vertScaleValue == 0; + gplotAutoscaler.setEnabled(enableAutoscale); + if (enableAutoscale) { + return; + } + yAxisLowerLim = -_vertScaleValue; + yAxisUpperLim = _vertScaleValue; + plot.setYLim(yAxisLowerLim, yAxisUpperLim); + //Update button text + customYLim(yAxisMin, yAxisLowerLim); + customYLim(yAxisMax, yAxisUpperLim); + } + + //Update yAxis text and responsively size Textfield + private void customYLim(TextBox tb, int limit) { + StringBuilder s = new StringBuilder(limit > 0 ? "+" : ""); + s.append(limit); + s.append("uV"); + tb.setText(s.toString()); + } + + public void resize(int _x, int _y, int _w, int _h) { + x = _x; + y = _y; + w = _w; + h = _h; + + //reposition & resize the plot + int plotW = w - uiSpaceWidth; + plot.setPos(x + uiSpaceWidth, y); + plot.setDim(plotW, h); + + int padding = 2; + voltageValue.setPosition(x + uiSpaceWidth + (w - uiSpaceWidth) - padding, y + h); + impValue.setPosition(x + uiSpaceWidth + (int)plot.getDim()[0], y + padding); + + yAxisMax.setPosition(x + uiSpaceWidth + padding, y + int(padding*1.5) - 2); + yAxisMin.setPosition(x + uiSpaceWidth + padding, y + h - yAxisLabel_h - padding - 1); + + final int yAxisLabelWidth = yAxisMax.getWidth(); + int yScaleButtonX = x + uiSpaceWidth + padding_4; + int yScaleButtonY = y + h/2 - yScaleButton_h/2; + boolean enoughSpaceBetweenAxisLabels = h > yScaleButton_h + yAxisLabel_h*2 + 2; + yScaleButtonX += enoughSpaceBetweenAxisLabels ? 0 : yAxisLabelWidth; + yScaleButton_neg.setPosition(yScaleButtonX, yScaleButtonY); + yScaleButtonX += yScaleButton_w + padding; + yScaleButton_pos.setPosition(yScaleButtonX, yScaleButtonY); + + onOff_diameter = h > 26 ? 26 : h - 2; + onOffButton.setSize(onOff_diameter, onOff_diameter); + onOffButton.setPosition(x + 6, y + int(h/2) - int(onOff_diameter/2)); + } + + public void updateCP5(PApplet _parentApplet) { + cbCp5.setGraphics(_parentApplet, 0, 0); + } + + private boolean isBottomChannel() { + int numActiveChannels = widgetManager.getTimeSeriesWidget().tsChanSelect.getActiveChannels().size(); + boolean isLastChannel = channelIndex == widgetManager.getTimeSeriesWidget().tsChanSelect.getActiveChannels().get(numActiveChannels - 1); + return isLastChannel; + } + + public void mousePressed() { + } + + public void mouseReleased() { + } + + private void createOnOffButton(String name, String text, int _x, int _y, int _w, int _h) { + onOffButton = createButton(cbCp5, name, text, _x, _y, _w, _h, 0, h2, 16, channelColors[channelIndex%8], WHITE, BUTTON_HOVER, BUTTON_PRESSED, (Integer) null, -2); + onOffButton.setCircularButton(true); + onOffButton.onRelease(new CallbackListener() { + public void controlEvent(CallbackEvent theEvent) { + boolean newState = !currentBoard.isEXGChannelActive(channelIndex); + println("[" + channelString + "] onOff released - " + (newState ? "On" : "Off")); + currentBoard.setEXGChannelActive(channelIndex, newState); + if (currentBoard instanceof ADS1299SettingsBoard) { + W_TimeSeries timeSeriesWidget = widgetManager.getTimeSeriesWidget(); + timeSeriesWidget.adsSettingsController.updateChanSettingsDropdowns(channelIndex, currentBoard.isEXGChannelActive(channelIndex)); + boolean hasUnappliedChanges = currentBoard.isEXGChannelActive(channelIndex) != newState; + timeSeriesWidget.adsSettingsController.setHasUnappliedSettings(channelIndex, hasUnappliedChanges); + } + } + }); + onOffButton.setDescription("Click to toggle channel " + channelString + "."); + } + + private Button createYScaleButton(int chan, boolean shouldIncrease, String bName, String bText, int _x, int _y, int _w, int _h, PImage _default, PImage _hover, PImage _active) { + _default.resize(_w, _h); + _hover.resize(_w, _h); + _active.resize(_w, _h); + final Button myButton = cbCp5.addButton(bName) + .setPosition(_x, _y) + .setSize(_w, _h) + .setColorLabel(color(255)) + .setColorForeground(OPENBCI_BLUE) + .setColorBackground(color(144, 100)) + .setImages(_default, _hover, _active) + ; + myButton.onClick(new yScaleButtonCallbackListener(chan, shouldIncrease)); + return myButton; + } + + private class yScaleButtonCallbackListener implements CallbackListener { + private int channel; + private boolean increase; + private final int hardLimit = 10; + private int yLimOption = TimeSeriesYLim.UV_200.getValue(); + //private int delta = 0; //value to change limits by + + yScaleButtonCallbackListener(int theChannel, boolean isIncrease) { + channel = theChannel; + increase = isIncrease; + } + public void controlEvent(CallbackEvent theEvent) { + verbosePrint("A button was pressed for channel " + (channel+1) + ". Should we increase (or decrease?): " + increase); + + int inc = increase ? 1 : -1; + int factor = yAxisUpperLim > 25 || (yAxisUpperLim == 25 && increase) ? 25 : 5; + int n = (int)(log10(abs(yAxisLowerLim))) * factor * inc; + yAxisLowerLim -= n; + n = (int)(log10(yAxisUpperLim)) * factor * inc; + yAxisUpperLim += n; + + yAxisLowerLim = yAxisLowerLim <= -hardLimit ? yAxisLowerLim : -hardLimit; + yAxisUpperLim = yAxisUpperLim >= hardLimit ? yAxisUpperLim : hardLimit; + plot.setYLim(yAxisLowerLim, yAxisUpperLim); + //Update button text + customYLim(yAxisMin, yAxisLowerLim); + customYLim(yAxisMax, yAxisUpperLim); + } + } +}; + +//======================================================================================================================== +// END OF -- CHANNEL BAR CLASS +//======================================================================================================================== + + +//========================== PLAYBACKSLIDER ========================== +class PlaybackScrollbar { + private final float ps_Padding = 40.0; //used to make room for skip to start button + private int x, y, w, h; + private int swidth, sheight; // width and height of bar + private float xpos, ypos; // x and y position of bar + private float spos; // x position of slider + private float sposMin, sposMax; // max and min values of slider + private boolean over; // is the mouse over the slider? + private boolean locked; + private ControlP5 pbsb_cp5; + private Button skipToStartButton; + private int skipToStart_diameter; + private String currentAbsoluteTimeToDisplay = ""; + private String currentTimeInSecondsToDisplay = ""; + private FileBoard fileBoard; + + private final DateFormat currentTimeFormatShort = new SimpleDateFormat("mm:ss"); + private final DateFormat currentTimeFormatLong = new SimpleDateFormat("HH:mm:ss"); + private final DateFormat timeStampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + PlaybackScrollbar (int _x, int _y, int _w, int _h, float xp, float yp, int sw, int sh) { + x = _x; + y = _y; + w = _w; + h = _h; + swidth = sw; + sheight = sh; + xpos = xp + ps_Padding; //lots of padding to make room for button + ypos = yp-sheight/2; + spos = xpos; + sposMin = xpos; + sposMax = xpos + swidth - sheight/2; + + pbsb_cp5 = new ControlP5(ourApplet); + pbsb_cp5.setGraphics(ourApplet, 0,0); + pbsb_cp5.setAutoDraw(false); + + //Let's make a button to return to the start of playback!! + skipToStart_diameter = 25; + createSkipToStartButton("skipToStartButton", "", int(xp) + int(skipToStart_diameter*.5), int(yp) + int(sh/2) - skipToStart_diameter, skipToStart_diameter, skipToStart_diameter); + + fileBoard = (FileBoard)currentBoard; + } + + private void createSkipToStartButton(String name, String text, int _x, int _y, int _w, int _h) { + skipToStartButton = createButton(pbsb_cp5, name, text, _x, _y, _w, _h, 0, p5, 12, GREY_235, OPENBCI_DARKBLUE, BUTTON_HOVER, BUTTON_PRESSED, (Integer)null, 0); + PImage defaultImage = loadImage("skipToStart_default-30x26.png"); + skipToStartButton.setImage(defaultImage); + skipToStartButton.setForceDrawBackground(true); + skipToStartButton.onRelease(new CallbackListener() { + public void controlEvent(CallbackEvent theEvent) { + skipToStartButtonAction(); + } + }); + skipToStartButton.setDescription("Click to go back to the beginning of the file."); + } + + /////////////// Update loop for PlaybackScrollbar + void update() { + checkMouseOver(); // check if mouse is over + + if (mousePressed && over) { + locked = true; + } + if (!mousePressed) { + locked = false; + } + //if the slider is being used, update new position based on user mouseX + if (locked) { + spos = constrain(mouseX-sheight/2, sposMin, sposMax); + scrubToPosition(); + } + else { + updateCursor(); + } + + // update timestamp + currentAbsoluteTimeToDisplay = getAbsoluteTimeToDisplay(); + + //update elapsed time to display + currentTimeInSecondsToDisplay = getCurrentTimeToDisplaySeconds(); + + } //end update loop for PlaybackScrollbar + + void updateCursor() { + float currentSample = float(fileBoard.getCurrentSample()); + float totalSamples = float(fileBoard.getTotalSamples()); + float currentPlaybackPos = currentSample / totalSamples; + + spos = lerp(sposMin, sposMax, currentPlaybackPos); + } + + void scrubToPosition() { + int totalSamples = fileBoard.getTotalSamples(); + int newSamplePos = floor(totalSamples * getCursorPercentage()); + + fileBoard.goToIndex(newSamplePos); + dataProcessing.updateEntireDownsampledBuffer(); + dataProcessing.clearCalculatedMetricWidgets(); + } + + float getCursorPercentage() { + return (spos - sposMin) / (sposMax - sposMin); + } + + String getAbsoluteTimeToDisplay() { + List currentData = currentBoard.getData(1); + if (currentData.get(0).length == 0) { + return ""; + } + int timeStampChan = currentBoard.getTimestampChannel(); + long timestampMS = (long)(currentData.get(0)[timeStampChan] * 1000.0); + if (timestampMS == 0) { + return ""; + } + + return timeStampFormat.format(new Date(timestampMS)); + } + + String getCurrentTimeToDisplaySeconds() { + double totalMillis = fileBoard.getTotalTimeSeconds() * 1000.0; + double currentMillis = fileBoard.getCurrentTimeSeconds() * 1000.0; + + String totalTimeStr = formatCurrentTime(totalMillis); + String currentTimeStr = formatCurrentTime(currentMillis); + + return currentTimeStr + " / " + totalTimeStr; + } + + String formatCurrentTime(double millis) { + DateFormat formatter = currentTimeFormatShort; + if (millis >= 3600000.0) { // bigger than 60 minutes + formatter = currentTimeFormatLong; + } + + return formatter.format(new Date((long)millis)); + } + + //checks if mouse is over the playback scrollbar + private void checkMouseOver() { + if (mouseX > xpos && mouseX < xpos+swidth && + mouseY > ypos && mouseY < ypos+sheight) { + if (!over) { + onMouseEnter(); + } + } + else { + if (over) { + onMouseExit(); + } + } + } + + // called when the mouse enters the playback scrollbar + private void onMouseEnter() { + over = true; + cursor(HAND); //changes cursor icon to a hand + } + + private void onMouseExit() { + over = false; + cursor(ARROW); + } + + void draw() { + pushStyle(); + + fill(GREY_235); + stroke(OPENBCI_BLUE); + rect(x, y, w, h); + + //draw the playback slider inside the playback sub-widget + noStroke(); + fill(GREY_200); + rect(xpos, ypos, swidth, sheight); + + //select color for playback indicator + if (over || locked) { + fill(OPENBCI_DARKBLUE); + } else { + fill(102, 102, 102); + } + //draws playback position indicator + rect(spos, ypos, sheight/2, sheight); + + //draw current timestamp and X of Y Seconds above scrollbar + textFont(p2, 18); + fill(OPENBCI_DARKBLUE); + textAlign(LEFT, TOP); + float textHeight = textAscent() - textDescent(); + float textY = y - textHeight - 10; + float tw = textWidth(currentAbsoluteTimeToDisplay); + text(currentAbsoluteTimeToDisplay, xpos + swidth - tw, textY); + text(currentTimeInSecondsToDisplay, xpos, textY); + + popStyle(); + + pbsb_cp5.draw(); + } + + void screenResized(int _x, int _y, int _w, int _h, float _pbx, float _pby, float _pbw, float _pbh) { + x = _x; + y = _y; + w = _w; + h = _h; + swidth = int(_pbw); + sheight = int(_pbh); + xpos = _pbx + ps_Padding; //add lots of padding for use + ypos = _pby - sheight/2; + sposMin = xpos; + sposMax = xpos + swidth - sheight/2; + //update the position of the playback indicator us + //newspos = updatePos(); + + pbsb_cp5.setGraphics(ourApplet, 0, 0); + + skipToStartButton.setPosition( + int(_pbx) + int(skipToStart_diameter*.5), + int(_pby) - int(skipToStart_diameter*.5) + ); + } + + //This function scrubs to the beginning of the playback file + //Useful to 'reset' the scrollbar before loading a new playback file + void skipToStartButtonAction() { + fileBoard.goToIndex(0); + dataProcessing.updateEntireDownsampledBuffer(); + dataProcessing.clearCalculatedMetricWidgets(); + } + +};//end PlaybackScrollbar class + +//========================== TimeDisplay ========================== +class TimeDisplay { + int swidth, sheight; // width and height of bar + float xpos, ypos; // x and y position of bar + String currentAbsoluteTimeToDisplay = ""; + Boolean updatePosition = false; + LocalDateTime time; + + TimeDisplay (float xp, float yp, int sw, int sh) { + swidth = sw; + sheight = sh; + xpos = xp; //lots of padding to make room for button + ypos = yp; + currentAbsoluteTimeToDisplay = fetchCurrentTimeString(); + } + + /////////////// Update loop for TimeDisplay when data stream is running + void update() { + if (currentBoard.isStreaming()) { + //Fetch Local time + try { + currentAbsoluteTimeToDisplay = fetchCurrentTimeString(); + } catch (NullPointerException e) { + println("TimeDisplay: Timestamp error..."); + e.printStackTrace(); + } + + } + } //end update loop for TimeDisplay + + void draw() { + pushStyle(); + //draw current timestamp at the bottom of the Widget container + if (!currentAbsoluteTimeToDisplay.equals(null)) { + int fontSize = 17; + textFont(p2, fontSize); + fill(OPENBCI_DARKBLUE); + float tw = textWidth(currentAbsoluteTimeToDisplay); + text(currentAbsoluteTimeToDisplay, xpos + swidth - tw, ypos); + text(streamTimeElapsed.toString(), xpos + 10, ypos); + } + popStyle(); + } + + void screenResized(float _x, float _y, float _w, float _h) { + swidth = int(_w); + sheight = int(_h); + xpos = _x; + ypos = _y; + } + + String fetchCurrentTimeString() { + time = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); + return time.format(formatter); + } +};//end TimeDisplay class diff --git a/OpenBCI_GUI/W_TimeSeries.pde b/OpenBCI_GUI/W_TimeSeries.pde index d03658b0b..e686a590e 100644 --- a/OpenBCI_GUI/W_TimeSeries.pde +++ b/OpenBCI_GUI/W_TimeSeries.pde @@ -11,7 +11,7 @@ import org.apache.commons.lang3.math.NumberUtils; -class W_TimeSeries extends Widget { +class W_TimeSeries extends WidgetWithSettings { private int numChannelBars; private float xF, yF, wF, hF; private float ts_padding; @@ -31,10 +31,6 @@ class W_TimeSeries extends Widget { private PlaybackScrollbar scrollbar; private TimeDisplay timeDisplay; - TimeSeriesXLim xLimit = TimeSeriesXLim.FIVE; - TimeSeriesYLim yLimit = TimeSeriesYLim.AUTO; - TimeSeriesLabelMode labelMode = TimeSeriesLabelMode.MINIMAL; - private PImage expand_default; private PImage expand_hover; private PImage expand_active; @@ -47,7 +43,7 @@ class W_TimeSeries extends Widget { private boolean allowSpillover = false; private boolean hasScrollbar = true; //used to turn playback scrollbar widget on/off - List cp5ElementsToCheck = new ArrayList(); + List cp5ElementsToCheck; W_TimeSeries() { super(); @@ -56,13 +52,10 @@ class W_TimeSeries extends Widget { tscp5 = new ControlP5(ourApplet); tscp5.setGraphics(ourApplet, 0, 0); tscp5.setAutoDraw(false); - - tsChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); - //Activate all channels in channelSelect by default for this widget - tsChanSelect.activateAllButtons(); - + cp5ElementsToCheck = new ArrayList(); cp5ElementsToCheck.addAll(tsChanSelect.getCp5ElementsForOverlapCheck()); + xF = float(x); //float(int( ... is a shortcut for rounding the float down... so that it doesn't creep into the 1px margin yF = float(y); wF = float(w); @@ -76,17 +69,8 @@ class W_TimeSeries extends Widget { ts_h = hF - playbackWidgetHeight - plotBottomWell - (ts_padding*2); numChannelBars = globalChannelCount; //set number of channel bars = to current globalChannelCount of system (4, 8, or 16) - List verticalScaleList = EnumHelper.getEnumStrings(TimeSeriesYLim.class); - List horizontalScaleList = EnumHelper.getEnumStrings(TimeSeriesXLim.class); - List labelModeList = EnumHelper.getEnumStrings(TimeSeriesLabelMode.class); - - //This is a newer protocol for setting up dropdowns. - addDropdown("timeSeriesVerticalScaleDropdown", "Vert Scale", verticalScaleList, yLimit.getIndex()); - addDropdown("timeSeriesHorizontalScaleDropdown", "Window", horizontalScaleList, xLimit.getIndex()); - addDropdown("timeSeriesLabelModeDropdown", "Labels", labelModeList, labelMode.getIndex()); - //Instantiate scrollbar if using playback mode and scrollbar feature in use - if((currentBoard instanceof FileBoard) && hasScrollbar) { + if ((currentBoard instanceof FileBoard) && hasScrollbar) { playbackWidgetHeight = 30.0; int _x = floor(xF) - 1; int _y = int(ts_y + ts_h + playbackWidgetHeight + 5); @@ -114,11 +98,12 @@ class W_TimeSeries extends Widget { channelBarHeight = int(ts_h/numChannelBars); channelBars = new ChannelBar[numChannelBars]; //create our channel bars and populate our channelBars array! - for(int i = 0; i < numChannelBars; i++) { + for (int i = 0; i < numChannelBars; i++) { int channelBarY = int(ts_y) + i*(channelBarHeight); //iterate through bar locations ChannelBar tempBar = new ChannelBar(ourApplet, i, int(ts_x), channelBarY, int(ts_w), channelBarHeight, expand_default, expand_hover, expand_active, contract_default, contract_hover, contract_active); channelBars[i] = tempBar; } + applyVerticalScaleToChannelBars(); int x_hsc = int(channelBars[0].plot.getPos()[0] + 2); int y_hsc = int(channelBars[0].plot.getPos()[1]); @@ -130,8 +115,48 @@ class W_TimeSeries extends Widget { cp5ElementsToCheck.add((controlP5.Controller)hwSettingsButton); adsSettingsController = new ADS1299SettingsController(ourApplet, tsChanSelect.getActiveChannels(), x_hsc, y_hsc, w_hsc, h_hsc, channelBarHeight); } + } - setVerticalScale(yLimit.getIndex()); + @Override + protected void initWidgetSettings() { + super.initWidgetSettings(); + // Store default values for widget settings + widgetSettings.set(TimeSeriesYLim.class, TimeSeriesYLim.AUTO) + .set(TimeSeriesXLim.class, TimeSeriesXLim.FIVE) + .set(TimeSeriesLabelMode.class, TimeSeriesLabelMode.MINIMAL); + + // Initialize the dropdowns with these settings + initDropdown(TimeSeriesYLim.class, "timeSeriesVerticalScaleDropdown", "Vert Scale"); + initDropdown(TimeSeriesXLim.class, "timeSeriesHorizontalScaleDropdown", "Window"); + initDropdown(TimeSeriesLabelMode.class, "timeSeriesLabelModeDropdown", "Labels"); + + // Initialize the channel select feature for this widget + tsChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); + //Activate all channels in channelSelect by default for this widget + tsChanSelect.activateAllButtons(); + + saveActiveChannels(tsChanSelect.getActiveChannels()); + + widgetSettings.saveDefaults(); + } + + @Override + protected void applySettings() { + // Update dropdown labels to match current settings + updateDropdownLabel(TimeSeriesYLim.class, "timeSeriesVerticalScaleDropdown"); + updateDropdownLabel(TimeSeriesXLim.class, "timeSeriesHorizontalScaleDropdown"); + updateDropdownLabel(TimeSeriesLabelMode.class, "timeSeriesLabelModeDropdown"); + applyVerticalScaleToChannelBars(); + applyHorizontalScaleToChannelBars(); + applyActiveChannels(tsChanSelect); + } + + @Override + protected void updateChannelSettings() { + // Just save the current active channels when saving settings + if (tsChanSelect != null) { + saveActiveChannels(tsChanSelect.getActiveChannels()); + } } void update() { @@ -152,13 +177,13 @@ class W_TimeSeries extends Widget { tsChanSelect.update(x, y, w); //Update and resize all active channels - for(int i = 0; i < tsChanSelect.getActiveChannels().size(); i++) { + for (int i = 0; i < tsChanSelect.getActiveChannels().size(); i++) { int activeChannel = tsChanSelect.getActiveChannels().get(i); int channelBarY = int(ts_y + chanSelectOffset) + i*(channelBarHeight); //iterate through bar locations //To make room for channel bar separator, subtract space between channel bars from height int cb_h = channelBarHeight - INTER_CHANNEL_BAR_SPACE; channelBars[activeChannel].resize(int(ts_x), channelBarY, int(ts_w), cb_h); - channelBars[activeChannel].update(getAdsSettingsVisible(), labelMode); + channelBars[activeChannel].update(getAdsSettingsVisible(), widgetSettings.get(TimeSeriesLabelMode.class)); } //Responsively size and update the HardwareSettingsController @@ -170,7 +195,7 @@ class W_TimeSeries extends Widget { } //Update Playback scrollbar and/or display time - if((currentBoard instanceof FileBoard) && hasScrollbar) { + if ((currentBoard instanceof FileBoard) && hasScrollbar) { //scrub playback file scrollbar.update(); } else { @@ -187,7 +212,7 @@ class W_TimeSeries extends Widget { //draw channel bars for (int i = 0; i < tsChanSelect.getActiveChannels().size(); i++) { int activeChannel = tsChanSelect.getActiveChannels().get(i); - channelBars[activeChannel].draw(getAdsSettingsVisible(), labelMode); + channelBars[activeChannel].draw(getAdsSettingsVisible()); } //Display playback scrollbar, timeDisplay, or ADSSettingsController depending on data source @@ -227,7 +252,7 @@ class W_TimeSeries extends Widget { ts_h = hF - playbackWidgetHeight - plotBottomWell - (ts_padding*2); ////Resize the playback slider if using playback mode, or resize timeDisplay div at the bottom of timeSeries - if((currentBoard instanceof FileBoard) && hasScrollbar) { + if ((currentBoard instanceof FileBoard) && hasScrollbar) { int _x = floor(xF) - 1; int _y = y + h - int(playbackWidgetHeight); int _w = int(wF) + 1; @@ -252,7 +277,7 @@ class W_TimeSeries extends Widget { cb.updateCP5(ourApplet); } - for(int i = 0; i < tsChanSelect.getActiveChannels().size(); i++) { + for (int i = 0; i < tsChanSelect.getActiveChannels().size(); i++) { int activeChannel = tsChanSelect.getActiveChannels().get(i); int channelBarY = int(ts_y + chanSelectOffset) + i*(channelBarHeight); //iterate through bar locations channelBars[activeChannel].resize(int(ts_x), channelBarY, int(ts_w), channelBarHeight); //bar x, bar y, bar w, bar h @@ -268,7 +293,7 @@ class W_TimeSeries extends Widget { super.mousePressed(); tsChanSelect.mousePressed(this.dropdownIsActive); //Calls channel select mousePressed and checks if clicked - for(int i = 0; i < tsChanSelect.getActiveChannels().size(); i++) { + for (int i = 0; i < tsChanSelect.getActiveChannels().size(); i++) { int activeChannel = tsChanSelect.getActiveChannels().get(i); channelBars[activeChannel].mousePressed(); } @@ -277,14 +302,14 @@ class W_TimeSeries extends Widget { void mouseReleased() { super.mouseReleased(); - for(int i = 0; i < tsChanSelect.getActiveChannels().size(); i++) { + for (int i = 0; i < tsChanSelect.getActiveChannels().size(); i++) { int activeChannel = tsChanSelect.getActiveChannels().get(i); channelBars[activeChannel].mouseReleased(); } } public void setAdsSettingsVisible(boolean visible) { - if(!(currentBoard instanceof ADS1299SettingsBoard)) { + if (!(currentBoard instanceof ADS1299SettingsBoard)) { return; } @@ -331,34 +356,32 @@ class W_TimeSeries extends Widget { return myButton; } - public TimeSeriesYLim getVerticalScale() { - return yLimit; - } - - public TimeSeriesXLim getHorizontalScale() { - return xLimit; + private void applyVerticalScaleToChannelBars() { + int verticalScaleValue = widgetSettings.get(TimeSeriesYLim.class).getValue(); + for (int i = 0; i < numChannelBars; i++) { + channelBars[i].adjustVertScale(verticalScaleValue); + } } - public TimeSeriesLabelMode getLabelMode() { - return labelMode; + private void applyHorizontalScaleToChannelBars() { + int horizontalScaleValue = widgetSettings.get(TimeSeriesXLim.class).getValue(); + for (int i = 0; i < numChannelBars; i++) { + channelBars[i].adjustTimeAxis(horizontalScaleValue); + } } public void setVerticalScale(int n) { - yLimit = yLimit.values()[n]; - for (int i = 0; i < numChannelBars; i++) { - channelBars[i].adjustVertScale(yLimit.getValue()); - } + widgetSettings.setByIndex(TimeSeriesYLim.class, n); + applyVerticalScaleToChannelBars(); } public void setHorizontalScale(int n) { - xLimit = xLimit.values()[n]; - for (int i = 0; i < numChannelBars; i++) { - channelBars[i].adjustTimeAxis(xLimit.getValue()); - } + widgetSettings.setByIndex(TimeSeriesXLim.class, n); + applyHorizontalScaleToChannelBars(); } public void setLabelMode(int n) { - labelMode = labelMode.values()[n]; + widgetSettings.setByIndex(TimeSeriesLabelMode.class, n); } }; @@ -371,724 +394,6 @@ void timeSeriesHorizontalScaleDropdown(int n) { widgetManager.getTimeSeriesWidget().setHorizontalScale(n); } -void LabelMode_TS(int n) { +void timeSeriesLabelModeDropdown(int n) { widgetManager.getTimeSeriesWidget().setLabelMode(n); } - -//======================================================================================================================== -// CHANNEL BAR CLASS -- Implemented by Time Series Widget Class -//======================================================================================================================== -//this class contains the plot and buttons for a single channel of the Time Series widget -//one of these will be created for each channel (4, 8, or 16) -class ChannelBar { - - int channelIndex; //duh - String channelString; - int x, y, w, h; - int defaultH; - ControlP5 cbCp5; - Button onOffButton; - int onOff_diameter; - int yScaleButton_h; - int yScaleButton_w; - Button yScaleButton_pos; - Button yScaleButton_neg; - int yAxisLabel_h; - private TextBox yAxisMax; - private TextBox yAxisMin; - - int yAxisUpperLim; - int yAxisLowerLim; - int uiSpaceWidth; - int padding_4 = 4; - int minimumChannelHeight; - int plotBottomWellH = 35; - - GPlot plot; //the actual grafica-based GPlot that will be rendering the Time Se ries trace - GPointsArray channelPoints; - int nPoints; - int numSeconds; - float timeBetweenPoints; - private GPlotAutoscaler gplotAutoscaler; - - color channelColor; //color of plot trace - - TextBox voltageValue; - TextBox impValue; - - boolean drawVoltageValue; - - ChannelBar(PApplet _parentApplet, int _channelIndex, int _x, int _y, int _w, int _h, PImage expand_default, PImage expand_hover, PImage expand_active, PImage contract_default, PImage contract_hover, PImage contract_active) { - - cbCp5 = new ControlP5(ourApplet); - cbCp5.setGraphics(ourApplet, x, y); - cbCp5.setAutoDraw(false); //Setting this saves code as cp5 elements will only be drawn/visible when [cp5].draw() is called - - channelIndex = _channelIndex; - channelString = str(channelIndex + 1); - - x = _x; - y = _y; - w = _w; - h = _h; - defaultH = h; - - onOff_diameter = h > 26 ? 26 : h - 2; - createOnOffButton("onOffButton"+channelIndex, channelString, x + 6, y + int(h/2) - int(onOff_diameter/2), onOff_diameter, onOff_diameter); - - //Create GPlot for this Channel - uiSpaceWidth = 36 + padding_4; - yAxisUpperLim = 200; - yAxisLowerLim = -200; - numSeconds = 5; - plot = new GPlot(_parentApplet); - plot.setPos(x + uiSpaceWidth, y); - plot.setDim(w - uiSpaceWidth, h); - plot.setMar(0f, 0f, 0f, 0f); - plot.setLineColor((int)channelColors[channelIndex%8]); - plot.setXLim(-5,0); - plot.setYLim(yAxisLowerLim, yAxisUpperLim); - plot.setPointSize(2); - plot.setPointColor(0); - plot.setAllFontProperties("Arial", 0, 14); - plot.getXAxis().setFontColor(OPENBCI_DARKBLUE); - plot.getXAxis().setLineColor(OPENBCI_DARKBLUE); - plot.getXAxis().getAxisLabel().setFontColor(OPENBCI_DARKBLUE); - if(channelIndex == globalChannelCount-1) { - plot.getXAxis().setAxisLabelText("Time (s)"); - plot.getXAxis().getAxisLabel().setOffset(plotBottomWellH/2 + 5f); - } - gplotAutoscaler = new GPlotAutoscaler(); - - //Fill the GPlot with initial data - nPoints = nPointsBasedOnDataSource(); - channelPoints = new GPointsArray(nPoints); - timeBetweenPoints = (float)numSeconds / (float)nPoints; - for (int i = 0; i < nPoints; i++) { - float time = -(float)numSeconds + (float)i*timeBetweenPoints; - float filt_uV_value = 0.0; //0.0 for all points to start - GPoint tempPoint = new GPoint(time, filt_uV_value); - channelPoints.set(i, tempPoint); - } - plot.setPoints(channelPoints); //set the plot with 0.0 for all channelPoints to start - - //Create a UI to custom scale the Y axis for this channel - yScaleButton_w = 18; - yScaleButton_h = 18; - yAxisLabel_h = 12; - int padding = 2; - yAxisMax = new TextBox("+"+yAxisUpperLim+"uV", x + uiSpaceWidth + padding, y + int(padding*1.5), OPENBCI_DARKBLUE, color(255,255,255,175), LEFT, TOP); - yAxisMin = new TextBox(yAxisLowerLim+"uV", x + uiSpaceWidth + padding, y + h - yAxisLabel_h - padding_4, OPENBCI_DARKBLUE, color(255,255,255,175), LEFT, TOP); - customYLim(yAxisMax, yAxisUpperLim); - customYLim(yAxisMin, yAxisLowerLim); - yScaleButton_neg = createYScaleButton(channelIndex, false, "decreaseYscale", "-T", x + uiSpaceWidth + padding, y + w/2 - yScaleButton_h/2, yScaleButton_w, yScaleButton_h, contract_default, contract_hover, contract_active); - yScaleButton_pos = createYScaleButton(channelIndex, true, "increaseYscale", "+T", x + uiSpaceWidth + padding*2 + yScaleButton_w, y + w/2 - yScaleButton_h/2, yScaleButton_w, yScaleButton_h, expand_default, expand_hover, expand_active); - - //Create textBoxes to display the current values - impValue = new TextBox("", x + uiSpaceWidth + (int)plot.getDim()[0], y + padding, OPENBCI_DARKBLUE, color(255,255,255,175), RIGHT, TOP); - voltageValue = new TextBox("", x + uiSpaceWidth + (int)plot.getDim()[0] - padding, y + h, OPENBCI_DARKBLUE, color(255,255,255,175), RIGHT, BOTTOM); - drawVoltageValue = true; - - //Establish a minimumChannelHeight - minimumChannelHeight = padding_4 + yAxisLabel_h*2; - } - - void update(boolean hardwareSettingsAreOpen, TimeSeriesLabelMode _labelMode) { - - //Reusable variables - String fmt; float val; - - //Update the voltage value TextBox - val = dataProcessing.data_std_uV[channelIndex]; - voltageValue.string = String.format(getFmt(val),val) + " uVrms"; - if (is_railed != null) { - voltageValue.setText(is_railed[channelIndex].notificationString + voltageValue.string); - voltageValue.setTextColor(OPENBCI_DARKBLUE); - color bgColor = color(255,255,255,175); // Default white background for voltage TextBox - if (is_railed[channelIndex].is_railed) { - bgColor = SIGNAL_CHECK_RED_LOWALPHA; - } else if (is_railed[channelIndex].is_railed_warn) { - bgColor = SIGNAL_CHECK_YELLOW_LOWALPHA; - } - voltageValue.setBackgroundColor(bgColor); - } - - //update the impedance values - val = data_elec_imp_ohm[channelIndex]/1000; - fmt = String.format(getFmt(val),val) + " kOhm"; - if (is_railed != null && is_railed[channelIndex].is_railed == true) { - fmt = "RAILED - " + fmt; - } - impValue.setText(fmt); - - // update data in plot - updatePlotPoints(); - - if(currentBoard.isEXGChannelActive(channelIndex)) { - onOffButton.setColorBackground(channelColors[channelIndex%8]); // power down == false, set color to vibrant - } - else { - onOffButton.setColorBackground(50); // power down == true, set to grey - } - - //Hide yAxisButtons when hardware settings are open, using autoscale, and labels are turn on - boolean b = !hardwareSettingsAreOpen - && h > minimumChannelHeight - && !gplotAutoscaler.getEnabled() - && _labelMode == TimeSeriesLabelMode.ON; - yScaleButton_pos.setVisible(b); - yScaleButton_neg.setVisible(b); - yScaleButton_pos.setUpdate(b); - yScaleButton_neg.setUpdate(b); - b = !hardwareSettingsAreOpen - && h > minimumChannelHeight - && _labelMode == TimeSeriesLabelMode.ON; - yAxisMin.setVisible(b); - yAxisMax.setVisible(b); - voltageValue.setVisible(_labelMode != TimeSeriesLabelMode.OFF); - } - - private String getFmt(float val) { - String fmt; - if (val > 100.0f) { - fmt = "%.0f"; - } else if (val > 10.0f) { - fmt = "%.1f"; - } else { - fmt = "%.2f"; - } - return fmt; - } - - private void updatePlotPoints() { - float[][] buffer = downsampledFilteredBuffer.getBuffer(); - final int bufferSize = buffer[channelIndex].length; - final int startIndex = bufferSize - nPoints; - for (int i = startIndex; i < bufferSize; i++) { - int adjustedIndex = i - startIndex; - float time = -(float)numSeconds + (float)(adjustedIndex)*timeBetweenPoints; - float filt_uV_value = buffer[channelIndex][i]; - channelPoints.set(adjustedIndex, time, filt_uV_value, ""); - } - plot.setPoints(channelPoints); - - gplotAutoscaler.update(plot, channelPoints); - - if (gplotAutoscaler.getEnabled()) { - float[] minMax = gplotAutoscaler.getMinMax(); - customYLim(yAxisMin, (int)minMax[0]); - customYLim(yAxisMax, (int)minMax[1]); - } - } - - public void draw(boolean hardwareSettingsAreOpen, TimeSeriesLabelMode _labelMode) { - - plot.beginDraw(); - plot.drawBox(); - plot.drawGridLines(GPlot.VERTICAL); - try { - plot.drawLines(); - } catch (NullPointerException e) { - e.printStackTrace(); - println("PLOT ERROR ON CHANNEL " + channelIndex); - - } - //Draw the x axis label on the bottom channel bar, hide if hardware settings are open - if (isBottomChannel() && !hardwareSettingsAreOpen) { - plot.drawXAxis(); - plot.getXAxis().draw(); - } - plot.endDraw(); - - //draw channel holder background - pushStyle(); - stroke(OPENBCI_BLUE_ALPHA50); - noFill(); - rect(x,y,w,h); - popStyle(); - - //draw channelBar separator line in the middle of INTER_CHANNEL_BAR_SPACE - if (!isBottomChannel()) { - pushStyle(); - stroke(OPENBCI_DARKBLUE); - strokeWeight(1); - int separator_y = y + h + int(widgetManager.getTimeSeriesWidget().INTER_CHANNEL_BAR_SPACE / 2); - line(x, separator_y, x + w, separator_y); - popStyle(); - } - - //draw impedance values in time series also for each channel - drawVoltageValue = true; - if (currentBoard instanceof ImpedanceSettingsBoard) { - if(((ImpedanceSettingsBoard)currentBoard).isCheckingImpedance(channelIndex)) { - impValue.draw(); - drawVoltageValue = false; - } - } - - if (drawVoltageValue) { - voltageValue.draw(); - } - - try { - cbCp5.draw(); - } catch (NullPointerException e) { - e.printStackTrace(); - println("CP5 ERROR ON CHANNEL " + channelIndex); - } - - yAxisMin.draw(); - yAxisMax.draw(); - } - - private int nPointsBasedOnDataSource() { - return (numSeconds * currentBoard.getSampleRate()) / getDownsamplingFactor(); - } - - public void adjustTimeAxis(int _newTimeSize) { - numSeconds = _newTimeSize; - plot.setXLim(-_newTimeSize,0); - - nPoints = nPointsBasedOnDataSource(); - channelPoints = new GPointsArray(nPoints); - timeBetweenPoints = (float)numSeconds / (float)nPoints; - if(_newTimeSize > 1) { - plot.getXAxis().setNTicks(_newTimeSize); //sets the number of axis divisions... - }else{ - plot.getXAxis().setNTicks(10); - } - - updatePlotPoints(); - } - - public void adjustVertScale(int _vertScaleValue) { - boolean enableAutoscale = _vertScaleValue == 0; - gplotAutoscaler.setEnabled(enableAutoscale); - if (enableAutoscale) { - return; - } - yAxisLowerLim = -_vertScaleValue; - yAxisUpperLim = _vertScaleValue; - plot.setYLim(yAxisLowerLim, yAxisUpperLim); - //Update button text - customYLim(yAxisMin, yAxisLowerLim); - customYLim(yAxisMax, yAxisUpperLim); - } - - //Update yAxis text and responsively size Textfield - private void customYLim(TextBox tb, int limit) { - StringBuilder s = new StringBuilder(limit > 0 ? "+" : ""); - s.append(limit); - s.append("uV"); - tb.setText(s.toString()); - } - - public void resize(int _x, int _y, int _w, int _h) { - x = _x; - y = _y; - w = _w; - h = _h; - - //reposition & resize the plot - int plotW = w - uiSpaceWidth; - plot.setPos(x + uiSpaceWidth, y); - plot.setDim(plotW, h); - - int padding = 2; - voltageValue.setPosition(x + uiSpaceWidth + (w - uiSpaceWidth) - padding, y + h); - impValue.setPosition(x + uiSpaceWidth + (int)plot.getDim()[0], y + padding); - - yAxisMax.setPosition(x + uiSpaceWidth + padding, y + int(padding*1.5) - 2); - yAxisMin.setPosition(x + uiSpaceWidth + padding, y + h - yAxisLabel_h - padding - 1); - - final int yAxisLabelWidth = yAxisMax.getWidth(); - int yScaleButtonX = x + uiSpaceWidth + padding_4; - int yScaleButtonY = y + h/2 - yScaleButton_h/2; - boolean enoughSpaceBetweenAxisLabels = h > yScaleButton_h + yAxisLabel_h*2 + 2; - yScaleButtonX += enoughSpaceBetweenAxisLabels ? 0 : yAxisLabelWidth; - yScaleButton_neg.setPosition(yScaleButtonX, yScaleButtonY); - yScaleButtonX += yScaleButton_w + padding; - yScaleButton_pos.setPosition(yScaleButtonX, yScaleButtonY); - - onOff_diameter = h > 26 ? 26 : h - 2; - onOffButton.setSize(onOff_diameter, onOff_diameter); - onOffButton.setPosition(x + 6, y + int(h/2) - int(onOff_diameter/2)); - } - - public void updateCP5(PApplet _parentApplet) { - cbCp5.setGraphics(_parentApplet, 0, 0); - } - - private boolean isBottomChannel() { - int numActiveChannels = widgetManager.getTimeSeriesWidget().tsChanSelect.getActiveChannels().size(); - boolean isLastChannel = channelIndex == widgetManager.getTimeSeriesWidget().tsChanSelect.getActiveChannels().get(numActiveChannels - 1); - return isLastChannel; - } - - public void mousePressed() { - } - - public void mouseReleased() { - } - - private void createOnOffButton(String name, String text, int _x, int _y, int _w, int _h) { - onOffButton = createButton(cbCp5, name, text, _x, _y, _w, _h, 0, h2, 16, channelColors[channelIndex%8], WHITE, BUTTON_HOVER, BUTTON_PRESSED, (Integer) null, -2); - onOffButton.setCircularButton(true); - onOffButton.onRelease(new CallbackListener() { - public void controlEvent(CallbackEvent theEvent) { - boolean newState = !currentBoard.isEXGChannelActive(channelIndex); - println("[" + channelString + "] onOff released - " + (newState ? "On" : "Off")); - currentBoard.setEXGChannelActive(channelIndex, newState); - if (currentBoard instanceof ADS1299SettingsBoard) { - W_TimeSeries timeSeriesWidget = widgetManager.getTimeSeriesWidget(); - timeSeriesWidget.adsSettingsController.updateChanSettingsDropdowns(channelIndex, currentBoard.isEXGChannelActive(channelIndex)); - boolean hasUnappliedChanges = currentBoard.isEXGChannelActive(channelIndex) != newState; - timeSeriesWidget.adsSettingsController.setHasUnappliedSettings(channelIndex, hasUnappliedChanges); - } - } - }); - onOffButton.setDescription("Click to toggle channel " + channelString + "."); - } - - private Button createYScaleButton(int chan, boolean shouldIncrease, String bName, String bText, int _x, int _y, int _w, int _h, PImage _default, PImage _hover, PImage _active) { - _default.resize(_w, _h); - _hover.resize(_w, _h); - _active.resize(_w, _h); - final Button myButton = cbCp5.addButton(bName) - .setPosition(_x, _y) - .setSize(_w, _h) - .setColorLabel(color(255)) - .setColorForeground(OPENBCI_BLUE) - .setColorBackground(color(144, 100)) - .setImages(_default, _hover, _active) - ; - myButton.onClick(new yScaleButtonCallbackListener(chan, shouldIncrease)); - return myButton; - } - - private class yScaleButtonCallbackListener implements CallbackListener { - private int channel; - private boolean increase; - private final int hardLimit = 10; - private int yLimOption = TimeSeriesYLim.UV_200.getValue(); - //private int delta = 0; //value to change limits by - - yScaleButtonCallbackListener(int theChannel, boolean isIncrease) { - channel = theChannel; - increase = isIncrease; - } - public void controlEvent(CallbackEvent theEvent) { - verbosePrint("A button was pressed for channel " + (channel+1) + ". Should we increase (or decrease?): " + increase); - - int inc = increase ? 1 : -1; - int factor = yAxisUpperLim > 25 || (yAxisUpperLim == 25 && increase) ? 25 : 5; - int n = (int)(log10(abs(yAxisLowerLim))) * factor * inc; - yAxisLowerLim -= n; - n = (int)(log10(yAxisUpperLim)) * factor * inc; - yAxisUpperLim += n; - - yAxisLowerLim = yAxisLowerLim <= -hardLimit ? yAxisLowerLim : -hardLimit; - yAxisUpperLim = yAxisUpperLim >= hardLimit ? yAxisUpperLim : hardLimit; - plot.setYLim(yAxisLowerLim, yAxisUpperLim); - //Update button text - customYLim(yAxisMin, yAxisLowerLim); - customYLim(yAxisMax, yAxisUpperLim); - } - } -}; - -//======================================================================================================================== -// END OF -- CHANNEL BAR CLASS -//======================================================================================================================== - - -//========================== PLAYBACKSLIDER ========================== -class PlaybackScrollbar { - private final float ps_Padding = 40.0; //used to make room for skip to start button - private int x, y, w, h; - private int swidth, sheight; // width and height of bar - private float xpos, ypos; // x and y position of bar - private float spos; // x position of slider - private float sposMin, sposMax; // max and min values of slider - private boolean over; // is the mouse over the slider? - private boolean locked; - private ControlP5 pbsb_cp5; - private Button skipToStartButton; - private int skipToStart_diameter; - private String currentAbsoluteTimeToDisplay = ""; - private String currentTimeInSecondsToDisplay = ""; - private FileBoard fileBoard; - - private final DateFormat currentTimeFormatShort = new SimpleDateFormat("mm:ss"); - private final DateFormat currentTimeFormatLong = new SimpleDateFormat("HH:mm:ss"); - private final DateFormat timeStampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - PlaybackScrollbar (int _x, int _y, int _w, int _h, float xp, float yp, int sw, int sh) { - x = _x; - y = _y; - w = _w; - h = _h; - swidth = sw; - sheight = sh; - xpos = xp + ps_Padding; //lots of padding to make room for button - ypos = yp-sheight/2; - spos = xpos; - sposMin = xpos; - sposMax = xpos + swidth - sheight/2; - - pbsb_cp5 = new ControlP5(ourApplet); - pbsb_cp5.setGraphics(ourApplet, 0,0); - pbsb_cp5.setAutoDraw(false); - - //Let's make a button to return to the start of playback!! - skipToStart_diameter = 25; - createSkipToStartButton("skipToStartButton", "", int(xp) + int(skipToStart_diameter*.5), int(yp) + int(sh/2) - skipToStart_diameter, skipToStart_diameter, skipToStart_diameter); - - fileBoard = (FileBoard)currentBoard; - } - - private void createSkipToStartButton(String name, String text, int _x, int _y, int _w, int _h) { - skipToStartButton = createButton(pbsb_cp5, name, text, _x, _y, _w, _h, 0, p5, 12, GREY_235, OPENBCI_DARKBLUE, BUTTON_HOVER, BUTTON_PRESSED, (Integer)null, 0); - PImage defaultImage = loadImage("skipToStart_default-30x26.png"); - skipToStartButton.setImage(defaultImage); - skipToStartButton.setForceDrawBackground(true); - skipToStartButton.onRelease(new CallbackListener() { - public void controlEvent(CallbackEvent theEvent) { - skipToStartButtonAction(); - } - }); - skipToStartButton.setDescription("Click to go back to the beginning of the file."); - } - - /////////////// Update loop for PlaybackScrollbar - void update() { - checkMouseOver(); // check if mouse is over - - if (mousePressed && over) { - locked = true; - } - if (!mousePressed) { - locked = false; - } - //if the slider is being used, update new position based on user mouseX - if (locked) { - spos = constrain(mouseX-sheight/2, sposMin, sposMax); - scrubToPosition(); - } - else { - updateCursor(); - } - - // update timestamp - currentAbsoluteTimeToDisplay = getAbsoluteTimeToDisplay(); - - //update elapsed time to display - currentTimeInSecondsToDisplay = getCurrentTimeToDisplaySeconds(); - - } //end update loop for PlaybackScrollbar - - void updateCursor() { - float currentSample = float(fileBoard.getCurrentSample()); - float totalSamples = float(fileBoard.getTotalSamples()); - float currentPlaybackPos = currentSample / totalSamples; - - spos = lerp(sposMin, sposMax, currentPlaybackPos); - } - - void scrubToPosition() { - int totalSamples = fileBoard.getTotalSamples(); - int newSamplePos = floor(totalSamples * getCursorPercentage()); - - fileBoard.goToIndex(newSamplePos); - dataProcessing.updateEntireDownsampledBuffer(); - dataProcessing.clearCalculatedMetricWidgets(); - } - - float getCursorPercentage() { - return (spos - sposMin) / (sposMax - sposMin); - } - - String getAbsoluteTimeToDisplay() { - List currentData = currentBoard.getData(1); - if (currentData.get(0).length == 0) { - return ""; - } - int timeStampChan = currentBoard.getTimestampChannel(); - long timestampMS = (long)(currentData.get(0)[timeStampChan] * 1000.0); - if(timestampMS == 0) { - return ""; - } - - return timeStampFormat.format(new Date(timestampMS)); - } - - String getCurrentTimeToDisplaySeconds() { - double totalMillis = fileBoard.getTotalTimeSeconds() * 1000.0; - double currentMillis = fileBoard.getCurrentTimeSeconds() * 1000.0; - - String totalTimeStr = formatCurrentTime(totalMillis); - String currentTimeStr = formatCurrentTime(currentMillis); - - return currentTimeStr + " / " + totalTimeStr; - } - - String formatCurrentTime(double millis) { - DateFormat formatter = currentTimeFormatShort; - if (millis >= 3600000.0) { // bigger than 60 minutes - formatter = currentTimeFormatLong; - } - - return formatter.format(new Date((long)millis)); - } - - //checks if mouse is over the playback scrollbar - private void checkMouseOver() { - if (mouseX > xpos && mouseX < xpos+swidth && - mouseY > ypos && mouseY < ypos+sheight) { - if(!over) { - onMouseEnter(); - } - } - else { - if (over) { - onMouseExit(); - } - } - } - - // called when the mouse enters the playback scrollbar - private void onMouseEnter() { - over = true; - cursor(HAND); //changes cursor icon to a hand - } - - private void onMouseExit() { - over = false; - cursor(ARROW); - } - - void draw() { - pushStyle(); - - fill(GREY_235); - stroke(OPENBCI_BLUE); - rect(x, y, w, h); - - //draw the playback slider inside the playback sub-widget - noStroke(); - fill(GREY_200); - rect(xpos, ypos, swidth, sheight); - - //select color for playback indicator - if (over || locked) { - fill(OPENBCI_DARKBLUE); - } else { - fill(102, 102, 102); - } - //draws playback position indicator - rect(spos, ypos, sheight/2, sheight); - - //draw current timestamp and X of Y Seconds above scrollbar - textFont(p2, 18); - fill(OPENBCI_DARKBLUE); - textAlign(LEFT, TOP); - float textHeight = textAscent() - textDescent(); - float textY = y - textHeight - 10; - float tw = textWidth(currentAbsoluteTimeToDisplay); - text(currentAbsoluteTimeToDisplay, xpos + swidth - tw, textY); - text(currentTimeInSecondsToDisplay, xpos, textY); - - popStyle(); - - pbsb_cp5.draw(); - } - - void screenResized(int _x, int _y, int _w, int _h, float _pbx, float _pby, float _pbw, float _pbh) { - x = _x; - y = _y; - w = _w; - h = _h; - swidth = int(_pbw); - sheight = int(_pbh); - xpos = _pbx + ps_Padding; //add lots of padding for use - ypos = _pby - sheight/2; - sposMin = xpos; - sposMax = xpos + swidth - sheight/2; - //update the position of the playback indicator us - //newspos = updatePos(); - - pbsb_cp5.setGraphics(ourApplet, 0, 0); - - skipToStartButton.setPosition( - int(_pbx) + int(skipToStart_diameter*.5), - int(_pby) - int(skipToStart_diameter*.5) - ); - } - - //This function scrubs to the beginning of the playback file - //Useful to 'reset' the scrollbar before loading a new playback file - void skipToStartButtonAction() { - fileBoard.goToIndex(0); - dataProcessing.updateEntireDownsampledBuffer(); - dataProcessing.clearCalculatedMetricWidgets(); - } - -};//end PlaybackScrollbar class - -//========================== TimeDisplay ========================== -class TimeDisplay { - int swidth, sheight; // width and height of bar - float xpos, ypos; // x and y position of bar - String currentAbsoluteTimeToDisplay = ""; - Boolean updatePosition = false; - LocalDateTime time; - - TimeDisplay (float xp, float yp, int sw, int sh) { - swidth = sw; - sheight = sh; - xpos = xp; //lots of padding to make room for button - ypos = yp; - currentAbsoluteTimeToDisplay = fetchCurrentTimeString(); - } - - /////////////// Update loop for TimeDisplay when data stream is running - void update() { - if (currentBoard.isStreaming()) { - //Fetch Local time - try { - currentAbsoluteTimeToDisplay = fetchCurrentTimeString(); - } catch (NullPointerException e) { - println("TimeDisplay: Timestamp error..."); - e.printStackTrace(); - } - - } - } //end update loop for TimeDisplay - - void draw() { - pushStyle(); - //draw current timestamp at the bottom of the Widget container - if (!currentAbsoluteTimeToDisplay.equals(null)) { - int fontSize = 17; - textFont(p2, fontSize); - fill(OPENBCI_DARKBLUE); - float tw = textWidth(currentAbsoluteTimeToDisplay); - text(currentAbsoluteTimeToDisplay, xpos + swidth - tw, ypos); - text(streamTimeElapsed.toString(), xpos + 10, ypos); - } - popStyle(); - } - - void screenResized(float _x, float _y, float _w, float _h) { - swidth = int(_w); - sheight = int(_h); - xpos = _x; - ypos = _y; - } - - String fetchCurrentTimeString() { - time = LocalDateTime.now(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); - return time.format(formatter); - } -};//end TimeDisplay class diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index 723f3d48d..4ccc158e1 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -308,41 +308,135 @@ class Widget { }; //end of base Widget class abstract class WidgetWithSettings extends Widget { - // This class is used to add settings to a widget. It is a subclass of the Widget class. - // It is used to add settings to the widget and to save and load the settings from a file. - protected WidgetSettings widgetSettings; - + WidgetWithSettings() { super(); + // Create settings with the widget's title + widgetSettings = new WidgetSettings(getWidgetTitle()); + // Initialize settings with default values initWidgetSettings(); } - public void setWidgetSettings(WidgetSettings _widgetSettings) { - widgetSettings = _widgetSettings; - applySettings(); + /** + * Initialize widget settings with default values + * Override this method in widget subclasses to set custom defaults + */ + protected void initWidgetSettings() { + // Default implementation is empty + // Child classes should override this to add their specific settings } - public WidgetSettings getWidgetSettings() { + /** + * Apply current settings to the widget UI + * Override this method in subclasses to update UI elements based on settings + */ + protected abstract void applySettings(); + + /** + * Get the settings object for this widget + * @return WidgetSettings object for this widget + */ + public WidgetSettings getSettings() { return widgetSettings; } - - protected void initWidgetSettings() { - widgetSettings = new WidgetSettings(getWidgetTitle()); + + /** + * Convert widget settings to JSON string + * @return JSON representation of settings + */ + public String settingsToJSON() { + // If the widget has a channel selector, save its current state + updateChannelSettings(); + return widgetSettings.toJSON(); } - + + /** + * Load settings from JSON string + * @param jsonString JSON string containing settings + * @return true if settings were loaded successfully, false otherwise + */ + public boolean loadSettingsFromJSON(String jsonString) { + boolean success = widgetSettings.loadFromJSON(jsonString); + if (success) { + applySettings(); + } + return success; + } + + /** + * Helper method to initialize a dropdown with values from an enum + * @param enumClass Enum class to get values from + * @param id ID for the dropdown controller + * @param label Label to display above the dropdown + */ protected & IndexingInterface> void initDropdown(Class enumClass, String id, String label) { + T currentValue = widgetSettings.get(enumClass); + int currentIndex = currentValue != null ? currentValue.getIndex() : 0; List options = EnumHelper.getEnumStrings(enumClass); - int currentIndex = widgetSettings.get(enumClass).getIndex(); addDropdown(id, label, options, currentIndex); } + /** + * Helper method to update a dropdown label with current setting value + * @param enumClass Enum class to get the current value from + * @param controllerId ID of the controller to update + */ protected & IndexingInterface> void updateDropdownLabel(Class enumClass, String controllerId) { - String value = widgetSettings.get(enumClass).getString(); - cp5_widget.getController(controllerId).getCaptionLabel().setText(value); + T currentValue = widgetSettings.get(enumClass); + if (currentValue != null) { + String value = currentValue.getString(); + cp5_widget.getController(controllerId).getCaptionLabel().setText(value); + } + } + + /** + * Save active channel selection to widget settings + * @param channels List of selected channel indices + */ + protected void saveActiveChannels(List channels) { + widgetSettings.setActiveChannels(channels); + println(widgetTitle + ": Saved " + channels.size() + " active channels"); + } + + /** + * Apply saved active channel selection to a channel select component + * @param channelSelect The channel select component to update + * @return true if channels were loaded and applied, false otherwise + */ + protected boolean applyActiveChannels(ExGChannelSelect channelSelect) { + List savedChannels = widgetSettings.getActiveChannels(); + if (!savedChannels.isEmpty()) { + channelSelect.updateChannelSelection(savedChannels); + return true; + } + return false; + } + + /** + * Get the list of active channels from settings + * @return List of active channel indices, or empty list if none are saved + */ + protected List getActiveChannels() { + return widgetSettings.getActiveChannels(); + } + + /** + * Check if active channels are defined in settings + * @return true if active channels are defined, false otherwise + */ + protected boolean hasActiveChannels() { + return widgetSettings.hasActiveChannels(); + } + + /** + * Update channel settings from any channel selectors before saving + * Each widget class should override this if it has channel selectors + */ + protected void updateChannelSettings() { + // Default implementation does nothing + // Override in widgets that have channel selectors } - - protected abstract void applySettings(); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index 4a688e54b..860a86d5f 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -319,16 +319,33 @@ class WidgetManager { public String getWidgetSettingsAsJson() { StringBuilder allWidgetSettings = new StringBuilder(); allWidgetSettings.append("{"); + boolean firstWidget = true; + for (Widget widget : widgets) { if (!(widget instanceof WidgetWithSettings)) { continue; } - println("Found widget with settings: " + widget.getWidgetTitle()); - WidgetSettings widgetSettings = ((WidgetWithSettings) widget).getWidgetSettings(); + + WidgetWithSettings widgetWithSettings = (WidgetWithSettings) widget; + + // Call updateChannelSettings to ensure channel selections are saved + widgetWithSettings.updateChannelSettings(); + + String widgetTitle = widget.getWidgetTitle(); + WidgetSettings widgetSettings = widgetWithSettings.getSettings(); String json = widgetSettings.toJSON(); - allWidgetSettings.append("\"").append(widget.getWidgetTitle()).append("\": "); - allWidgetSettings.append(json).append(", "); + + // Only add comma if not the first widget + if (!firstWidget) { + allWidgetSettings.append(", "); + } else { + firstWidget = false; + } + + allWidgetSettings.append("\"").append(widgetTitle).append("\": "); + allWidgetSettings.append(json); } + allWidgetSettings.append("}"); return allWidgetSettings.toString(); } @@ -353,7 +370,7 @@ class WidgetManager { } String settingsJson = json.getString(widgetTitle, ""); - WidgetSettings widgetSettings = widgetWithSettings.getWidgetSettings(); + WidgetSettings widgetSettings = widgetWithSettings.getSettings(); boolean success = widgetSettings.loadFromJSON(settingsJson); if (!success) { println("WidgetManager:loadWidgetSettingsFromJson: Failed to load settings for " + widgetTitle); diff --git a/OpenBCI_GUI/WidgetSettings.pde b/OpenBCI_GUI/WidgetSettings.pde index ad3449e20..b6310574e 100644 --- a/OpenBCI_GUI/WidgetSettings.pde +++ b/OpenBCI_GUI/WidgetSettings.pde @@ -2,25 +2,43 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; /** - * Simple storage for widget settings that converts to/from JSON + * Unified settings storage for widgets that handles enum settings, channel selections, + * and other types of settings with JSON serialization */ class WidgetSettings { private String widgetName; - private HashMap> settings; + // Enum settings + private HashMap> enumSettings; private HashMap> defaults; + // Channel settings + private HashMap> channelSettings; + private HashMap> defaultChannelSettings; + // Other settings + private HashMap otherSettings; + private HashMap defaultOtherSettings; + + public static final String KEY_ACTIVE_CHANNELS = "activeChannels"; public WidgetSettings(String widgetName) { this.widgetName = widgetName; - this.settings = new HashMap>(); + this.enumSettings = new HashMap>(); this.defaults = new HashMap>(); + this.channelSettings = new HashMap>(); + this.defaultChannelSettings = new HashMap>(); + this.otherSettings = new HashMap(); + this.defaultOtherSettings = new HashMap(); } + // + // ENUM SETTINGS + // + /** * Store a setting using enum class as key * @return this WidgetSettings instance for method chaining */ public > WidgetSettings set(Class enumClass, T value) { - settings.put(enumClass.getName(), value); + enumSettings.put(enumClass.getName(), value); return this; } @@ -54,8 +72,8 @@ class WidgetSettings { */ public > T get(Class enumClass, T defaultValue) { String key = enumClass.getName(); - if (settings.containsKey(key)) { - Object value = settings.get(key); + if (enumSettings.containsKey(key)) { + Object value = enumSettings.get(key); if (value != null && enumClass.isInstance(value)) { return enumClass.cast(value); } @@ -67,34 +85,146 @@ class WidgetSettings { * Get a setting using enum class as key (returns null if not found) */ public > T get(Class enumClass) { - String key = enumClass.getName(); - if (settings.containsKey(key)) { - Object value = settings.get(key); - if (value != null && enumClass.isInstance(value)) { - return enumClass.cast(value); + return get(enumClass, null); + } + + // + // CHANNEL SETTINGS + // + + /** + * Store active channels + * @param channels List of selected channel indices + * @return this WidgetSettings instance for method chaining + */ + public WidgetSettings setActiveChannels(List channels) { + // Create a copy to prevent external modification + channelSettings.put(KEY_ACTIVE_CHANNELS, new ArrayList(channels)); + return this; + } + + /** + * Get active channels + * @return List of selected channel indices or empty list if not found + */ + public List getActiveChannels() { + if (channelSettings.containsKey(KEY_ACTIVE_CHANNELS)) { + // Return a copy to prevent external modification + return new ArrayList(channelSettings.get(KEY_ACTIVE_CHANNELS)); + } + return new ArrayList(); // Empty list if not found + } + + /** + * Check if active channels exist + * @return true if active channels exist, false otherwise + */ + public boolean hasActiveChannels() { + return channelSettings.containsKey(KEY_ACTIVE_CHANNELS) && + !channelSettings.get(KEY_ACTIVE_CHANNELS).isEmpty(); + } + + // + // OTHER SETTINGS + // + + /** + * Store a generic object setting with the given key + * @param key Name of the setting + * @param value Value to store + * @return this WidgetSettings instance for method chaining + */ + public WidgetSettings setObject(String key, T value) { + otherSettings.put(key, value); + return this; + } + + /** + * Get a generic object setting by key + * @param key Name of the setting to retrieve + * @param defaultValue Value to return if setting doesn't exist + * @return The stored value or the default if not found + */ + @SuppressWarnings("unchecked") + public T getObject(String key, T defaultValue) { + if (otherSettings.containsKey(key)) { + try { + return (T) otherSettings.get(key); + } catch (ClassCastException e) { + println("Type mismatch for setting " + key + ": " + e.getMessage()); } } - return null; + return defaultValue; + } + + /** + * Check if a generic object setting exists + * @param key Name of the setting to check + * @return true if the setting exists, false otherwise + */ + public boolean hasObject(String key) { + return otherSettings.containsKey(key); } + // + // DEFAULT HANDLING + // + /** * Save current settings as defaults * @return this WidgetSettings instance for method chaining */ public WidgetSettings saveDefaults() { - defaults = new HashMap>(settings); + // Save enum defaults + defaults = new HashMap>(enumSettings); + + // Save channel defaults + defaultChannelSettings = new HashMap>(); + saveDefaultChannels(); + + // Save other settings defaults + defaultOtherSettings = new HashMap(otherSettings); + return this; } + // Helper method for saving default channel settings + private void saveDefaultChannels() { + for (String key : channelSettings.keySet()) { + defaultChannelSettings.put(key, new ArrayList(channelSettings.get(key))); + } + } + /** * Restore to default settings * @return this WidgetSettings instance for method chaining */ public WidgetSettings restoreDefaults() { - settings = new HashMap>(defaults); + // Restore enum settings + enumSettings = new HashMap>(defaults); + + // Restore channel settings + restoreDefaultChannels(); + + // Restore other settings + otherSettings = new HashMap(defaultOtherSettings); + return this; } + // Helper method for restoring default channel settings + private void restoreDefaultChannels() { + channelSettings = new HashMap>(); + + for (String key : defaultChannelSettings.keySet()) { + channelSettings.put(key, new ArrayList(defaultChannelSettings.get(key))); + } + } + + // + // SERIALIZATION + // + /** * Convert settings to JSON string */ @@ -102,102 +232,253 @@ class WidgetSettings { JSONObject json = new JSONObject(); json.setString("widgetTitle", widgetName); - JSONArray items = new JSONArray(); - int i = 0; + // Serialize settings + serializeEnumSettings(json); + serializeChannelSettings(json); + serializeOtherSettings(json); - for (String key : settings.keySet()) { - Enum value = settings.get(key); + return json.toString(); + } + + // Helper method for enum serialization + private void serializeEnumSettings(JSONObject json) { + if (enumSettings.isEmpty()) { + return; + } + + JSONArray enumItems = new JSONArray(); + int i = 0; + for (String key : enumSettings.keySet()) { + Enum value = enumSettings.get(key); JSONObject item = new JSONObject(); item.setString("class", key); item.setString("value", value.name()); - items.setJSONObject(i++, item); + enumItems.setJSONObject(i++, item); + } + json.setJSONArray("enumSettings", enumItems); + } + + // Helper method for channel serialization + private void serializeChannelSettings(JSONObject json) { + if (channelSettings.isEmpty()) { + return; } - json.setJSONArray("settings", items); - return json.toString(); + JSONObject channelsJson = new JSONObject(); + for (String key : channelSettings.keySet()) { + List channels = channelSettings.get(key); + JSONArray channelArray = new JSONArray(); + + for (int i = 0; i < channels.size(); i++) { + channelArray.setInt(i, channels.get(i)); + } + channelsJson.setJSONArray(key, channelArray); + } + json.setJSONObject("channelSettings", channelsJson); + } + + // Helper method for other settings serialization + private void serializeOtherSettings(JSONObject json) { + if (otherSettings.isEmpty()) { + return; + } + + JSONObject othersJson = new JSONObject(); + + for (String key : otherSettings.keySet()) { + Object value = otherSettings.get(key); + + // Handle basic types that JSONObject supports + if (value instanceof String) { + othersJson.setString(key, (String)value); + } else if (value instanceof Integer) { + othersJson.setInt(key, (Integer)value); + } else if (value instanceof Float) { + othersJson.setFloat(key, (Float)value); + } else if (value instanceof Boolean) { + othersJson.setBoolean(key, (Boolean)value); + } + // Skip complex types that can't be directly serialized + } + + if (othersJson.size() > 0) { + json.setJSONObject("otherSettings", othersJson); + } } /** - * Attempts to load settings from a JSON string - * @param jsonString The JSON string containing settings - * @return true if settings were loaded successfully, false otherwise - */ + * Attempts to load settings from a JSON string + * @param jsonString The JSON string containing settings + * @return true if settings were loaded successfully, false otherwise + */ public boolean loadFromJSON(String jsonString) { try { JSONObject json = parseJSONObject(jsonString); - if (json == null) return false; + if (json == null) { + return false; + } + + validateWidgetName(json); - String loadedWidget = json.getString("widgetTitle", ""); - if (!loadedWidget.equals(widgetName)) { - println("Warning: Widget mismatch. Expected: " + widgetName + ", Found: " + loadedWidget); + boolean enumSuccess = loadEnumSettings(json); + boolean channelSuccess = loadChannelSettings(json); + boolean otherSuccess = loadOtherSettings(json); + + return enumSuccess || channelSuccess || otherSuccess; + } catch (Exception e) { + println("Error parsing JSON: " + e.getMessage()); + return false; + } + } + + // Helper method to validate widget name + private void validateWidgetName(JSONObject json) { + String loadedWidget = json.getString("widgetTitle", ""); + if (!loadedWidget.equals(widgetName)) { + println("Warning: Widget mismatch. Expected: " + widgetName + ", Found: " + loadedWidget); + } + } + + // Helper method to load enum settings + private boolean loadEnumSettings(JSONObject json) { + if (!json.hasKey("enumSettings")) { + return false; + } + + JSONArray enumItems = json.getJSONArray("enumSettings"); + if (enumItems == null) { + return false; + } + + boolean anySuccess = false; + for (int i = 0; i < enumItems.size(); i++) { + JSONObject item = enumItems.getJSONObject(i); + if (item == null) { + continue; } - JSONArray items = json.getJSONArray("settings"); - if (items != null) { - for (int i = 0; i < items.size(); i++) { - JSONObject item = items.getJSONObject(i); - String className = item.getString("class"); - String valueName = item.getString("value"); - - try { - Class enumClass = Class.forName(className); - if (enumClass.isEnum()) { - @SuppressWarnings("unchecked") - Enum enumValue = Enum.valueOf((Class)enumClass, valueName); - settings.put(className, enumValue); - } - } catch (Exception e) { - println("Error loading setting: " + e.getMessage()); - } - } - return true; + String className = item.getString("class", null); + String valueName = item.getString("value", null); + + if (className == null || valueName == null) { + continue; } + + anySuccess |= loadSingleEnum(className, valueName); + } + + return anySuccess; + } + + // Helper method to load a single enum value + private boolean loadSingleEnum(String className, String valueName) { + try { + Class enumClass = Class.forName(className); + if (!enumClass.isEnum()) { + return false; + } + + @SuppressWarnings("unchecked") + Enum enumValue = Enum.valueOf((Class)enumClass, valueName); + enumSettings.put(className, enumValue); + return true; } catch (Exception e) { - println("Error parsing JSON: " + e.getMessage()); + println("Error loading enum setting: " + e.getMessage()); + return false; } - return false; } -} -/** - * Example usage - */ - /* -class ExampleWidgetSettings extends WidgetSettings { - enum Mode { NORMAL, EXPERT, DEBUG } - enum Filter { NONE, LOW_PASS, HIGH_PASS, BAND_PASS } - - public ExampleWidgetSettings() { - super("Example"); - - // Set defaults - set(Mode.class, Mode.NORMAL); - set(Filter.class, Filter.NONE); - saveDefaults(); - } - - public void applyToUI() { - Mode mode = get(Mode.class, Mode.NORMAL); - Filter filter = get(Filter.class, Filter.NONE); - - // Apply to UI controls - println("Mode: " + mode + ", Filter: " + filter); - } - - public void exampleUsage() { - // Set some values - set(Mode.class, Mode.EXPERT); - set(Filter.class, Filter.BAND_PASS); - - // Convert to JSON - String json = toJSON(); - println("Settings JSON: " + json); - - // Restore defaults - restoreDefaults(); - - // Load from JSON - fromJSON(json); + // Helper method to load channel settings + private boolean loadChannelSettings(JSONObject json) { + if (!json.hasKey("channelSettings")) { + return false; + } + + JSONObject channelsJson = json.getJSONObject("channelSettings"); + // Fixed this line + if (channelsJson == null || channelsJson.size() == 0) { + return false; + } + + // Clear existing settings only when we have valid data + channelSettings.clear(); + + boolean anySuccess = false; + for (Object key : channelsJson.keys()) { + String channelKey = key.toString(); + JSONArray channelArray = channelsJson.getJSONArray(channelKey); + + if (channelArray == null) { + continue; + } + + List channels = new ArrayList(); + for (int i = 0; i < channelArray.size(); i++) { + channels.add(channelArray.getInt(i)); + } + + channelSettings.put(channelKey, channels); + anySuccess = true; + } + + return anySuccess; + } + + // Helper method to load other settings - simplified version + private boolean loadOtherSettings(JSONObject json) { + if (!json.hasKey("otherSettings")) { + return false; + } + + JSONObject othersJson = json.getJSONObject("otherSettings"); + if (othersJson == null || othersJson.size() == 0) { + return false; + } + + boolean anySuccess = false; + + // Get all keys + for (Object keyObj : othersJson.keys()) { + String key = keyObj.toString(); + Object value = null; + + // Try each type in sequence + value = tryLoadAnyType(othersJson, key); + if (value != null) { + otherSettings.put(key, value); + anySuccess = true; + } else { + println("Could not determine type for key: " + key); + } + } + + return anySuccess; + } + + /** + * Try to load a value from JSON as any supported type + */ + private Object tryLoadAnyType(JSONObject json, String key) { + // Try as String + try { + return json.getString(key); + } catch (Exception e) { /* Not a string */ } + + // Try as Integer + try { + return json.getInt(key); + } catch (Exception e) { /* Not an integer */ } + + // Try as Float + try { + return json.getFloat(key); + } catch (Exception e) { /* Not a float */ } + + // Try as Boolean + try { + return json.getBoolean(key); + } catch (Exception e) { /* Not a boolean */ } + + return null; // No type worked } } -*/ \ No newline at end of file From b5d8c32a07b5ebf842e0fbe67a7d820a81f032ba Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Fri, 18 Apr 2025 17:16:49 -0500 Subject: [PATCH 18/29] Refactor SessionSettings again after achieving functionality (for readability and maintainability) --- OpenBCI_GUI/SessionSettings.pde | 647 +++++++++++++++----------------- 1 file changed, 308 insertions(+), 339 deletions(-) diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde index 2884e84c5..ade5acb22 100644 --- a/OpenBCI_GUI/SessionSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -1,443 +1,412 @@ -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// This sketch saves and loads User Settings that appear during Sessions. -// -- All Time Series widget settings in Live, Playback, and Synthetic modes -// -- All FFT widget settings -// -- Default Layout, Board Mode, and other Global Settings -// -- Networking Mode and All settings for active networking protocol -// -- Accelerometer, Analog Read, Head Plot, Band Power, and Spectrogram -// -- Widget/Container Pairs -// -- OpenBCI Data Format Settings (.txt and .csv) -// Created: Richard Waltman - May/June 2018 -// -// -- Start System first! -// -- Lowercase 'n' to Save -// -- Capital 'N' to Load -// -- Functions saveGUIsettings() and loadGUISettings() are called: -// - during system initialization between checkpoints 4 and 5 -// - in Interactivty.pde with the rest of the keyboard shortcuts -// - in TopNav.pde when "Config" --> "Save Settings" || "Load Settings" is clicked -// -- This allows User to store snapshots of most GUI settings in Users/.../Documents/OpenBCI_GUI/Settings/ -// -- After loading, only a few actions are required: start/stop the data stream and networking streams, open/close serial port -// -// Tips on adding a new setting: -// -- figure out if the setting is Global, in an existing widget, or in a new class or widget -// -- read the comments -// -- once you find the right place to add your setting, you can copy the surrounding style -// -- uses JSON keys -// -- Example2: GUI version and settings version -// -- Requires new JSON key 'version` and settingsVersion -// -// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Refactored: Richard Waltman, April 2025 class SessionSettings { - //Current version to save to JSON + // Current version and configuration private String settingsVersion = "5.0.0"; - //for screen resizing + public int currentLayout; + + // Screen resizing variables public boolean screenHasBeenResized = false; public float timeOfLastScreenResize = 0; - public int widthOfLastScreen = 0; - public int heightOfLastScreen = 0; - //default layout variables - public int currentLayout; - //Used to time the GUI intro animation + public int widthOfLastScreen = 0, heightOfLastScreen = 0; + + // Animation timer public int introAnimationInit = 0; public final int INTRO_ANIMATION_DURATION = 2500; - - private final String[] USER_SETTINGS_FILES = { - "CytonUserSettings.json", - "DaisyUserSettings.json", - "GanglionUserSettings.json", - "PlaybackUserSettings.json", - "SynthFourUserSettings.json", - "SynthEightUserSettings.json", - "SynthSixteenUserSettings.json" - }; - private final String[] DEFAULT_SETTINGS_FILES = { - "CytonDefaultSettings.json", - "DaisyDefaultSettings.json", - "GanglionDefaultSettings.json", - "PlaybackDefaultSettings.json", - "SynthFourDefaultSettings.json", - "SynthEightDefaultSettings.json", - "SynthSixteenDefaultSettings.json" - }; - - //Primary JSON objects for saving and loading data + + // JSON data for saving/loading private JSONObject saveSettingsJSONData; private JSONObject loadSettingsJSONData; - - private final String GLOBAL_SETTINGS_KEY = "globalSettings"; - private final String GUI_VERSION_KEY = "guiVersion"; - private final String SESSION_SETTINGS_VERSION_KEY = "sessionSettingsVersion"; - private final String CHANNEL_COUNT_KEY = "channelCount"; - private final String DATA_SOURCE_KEY = "dataSource"; - private final String DATA_SMOOTHING_KEY = "dataSmoothing"; - private final String WIDGET_LAYOUT_KEY = "widgetLayout"; - private final String NETWORKING_KEY = "networking"; - private final String WIDGET_CONTAINER_SETTINGS_KEY = "widgetContainerSettings"; - private final String WIDGET_SETTINGS_KEY = "widgetSettings"; - + + // Dialog control variables + String saveDialogName; + String loadDialogName; + String controlEventDataSource; + + // Error handling boolean chanNumError = false; boolean dataSourceError = false; - - String saveDialogName; //Used when Save button is pressed - String loadDialogName; //Used when Load button is pressed - String controlEventDataSource; //Used for output message on system start - Boolean errorUserSettingsNotFound = false; //For error catching + boolean errorUserSettingsNotFound = false; + boolean loadErrorCytonEvent = false; int loadErrorTimerStart; - int loadErrorTimeWindow = 5000; //Time window in milliseconds to apply channel settings to Cyton board. This is to avoid a GUI crash at ~ 4500-5000 milliseconds. - Boolean loadErrorCytonEvent = false; - final int initTimeoutThreshold = 12000; //Timeout threshold in milliseconds - - SessionSettings() { - //Constructor - } + int loadErrorTimeWindow = 5000; + final int initTimeoutThreshold = 12000; + + // Constants for JSON keys + private final String + KEY_GLOBAL = "globalSettings", + KEY_VERSION = "guiVersion", + KEY_SETTINGS_VERSION = "sessionSettingsVersion", + KEY_CHANNELS = "channelCount", + KEY_DATA_SOURCE = "dataSource", + KEY_SMOOTHING = "dataSmoothing", + KEY_LAYOUT = "widgetLayout", + KEY_NETWORKING = "networking", + KEY_CONTAINERS = "widgetContainerSettings", + KEY_WIDGET_SETTINGS = "widgetSettings"; + + // File paths configuration + private final String[][] SETTING_FILES = { + {"CytonUserSettings.json", "CytonDefaultSettings.json"}, + {"DaisyUserSettings.json", "DaisyDefaultSettings.json"}, + {"GanglionUserSettings.json", "GanglionDefaultSettings.json"}, + {"PlaybackUserSettings.json", "PlaybackDefaultSettings.json"}, + {"SynthFourUserSettings.json", "SynthFourDefaultSettings.json"}, + {"SynthEightUserSettings.json", "SynthEightDefaultSettings.json"}, + {"SynthSixteenUserSettings.json", "SynthSixteenDefaultSettings.json"} + }; + private final int FILE_USER = 0, FILE_DEFAULT = 1; - //////////////////////////////////////////////////////////////// - // Init GUI Software Settings // - // // - // - Called during system initialization in OpenBCI_GUI.pde // - //////////////////////////////////////////////////////////////// + /** + * Initialize settings during system startup + */ void init() { - String defaultSettingsFileToSave = getPath("Default", eegDataSource, globalChannelCount); - - //Take a snapshot of the default GUI settings on every system init + String defaultFile = getPath("Default", eegDataSource, globalChannelCount); println("InitSettings: Saving Default Settings to file!"); try { - this.save(defaultSettingsFileToSave); //to avoid confusion with save() image + save(defaultFile); } catch (Exception e) { outputError("Failed to save Default Settings during Init. Please submit an Issue on GitHub."); e.printStackTrace(); } } - /////////////////////////////// - // Save GUI Settings // - /////////////////////////////// - void save(String saveGUISettingsFileLocation) { - - // Set up a JSON array + /** + * Save current settings to a file + */ + void save(String saveFilePath) { + // Create main JSON object and global settings saveSettingsJSONData = new JSONObject(); - - // Global Settings - JSONObject saveGlobalSettings = new JSONObject(); - saveGlobalSettings.setString(GUI_VERSION_KEY, localGUIVersionString); - saveGlobalSettings.setString(SESSION_SETTINGS_VERSION_KEY, settingsVersion); - saveGlobalSettings.setInt(CHANNEL_COUNT_KEY, globalChannelCount); - saveGlobalSettings.setInt(DATA_SOURCE_KEY, eegDataSource); + JSONObject globalSettings = new JSONObject(); + + // Add global settings + globalSettings.setString(KEY_VERSION, localGUIVersionString); + globalSettings.setString(KEY_SETTINGS_VERSION, settingsVersion); + globalSettings.setInt(KEY_CHANNELS, globalChannelCount); + globalSettings.setInt(KEY_DATA_SOURCE, eegDataSource); + globalSettings.setInt(KEY_LAYOUT, currentLayout); + + // Add data smoothing setting if applicable if (currentBoard instanceof SmoothingCapableBoard) { - saveGlobalSettings.setBoolean(DATA_SMOOTHING_KEY, ((SmoothingCapableBoard)currentBoard).getSmoothingActive()); + globalSettings.setBoolean(KEY_SMOOTHING, + ((SmoothingCapableBoard)currentBoard).getSmoothingActive()); } - saveGlobalSettings.setInt(WIDGET_LAYOUT_KEY, currentLayout); - saveSettingsJSONData.setJSONObject(GLOBAL_SETTINGS_KEY, saveGlobalSettings); - - // Networking Settings - JSONObject saveNetworkingSettings = parseJSONObject(dataProcessing.networkingSettings.getJson()); - saveSettingsJSONData.setJSONObject(NETWORKING_KEY, saveNetworkingSettings); - - // Widget layout settings - JSONObject saveWidgetLayout = new JSONObject(); + + // Add all settings to the main JSON object + saveSettingsJSONData.setJSONObject(KEY_GLOBAL, globalSettings); + saveSettingsJSONData.setJSONObject(KEY_NETWORKING, + parseJSONObject(dataProcessing.networkingSettings.getJson())); + saveSettingsJSONData.setJSONObject(KEY_CONTAINERS, saveWidgetContainerPositions()); + saveSettingsJSONData.setJSONObject(KEY_WIDGET_SETTINGS, + parseJSONObject(widgetManager.getWidgetSettingsAsJson())); + + // Save to file + saveJSONObject(saveSettingsJSONData, saveFilePath); + } + /** + * Save widget container positions + */ + private JSONObject saveWidgetContainerPositions() { + JSONObject widgetLayout = new JSONObject(); int numActiveWidgets = 0; - //Save what Widgets are active and respective Container number (see Containers.pde) - for (int i = 0; i < widgetManager.widgets.size(); i++) { //increment through all widgets - if (widgetManager.widgets.get(i).getIsActive()) { //If a widget is active... - numActiveWidgets++; //increment numActiveWidgets - //println("Widget" + i + " is active"); - // activeWidgets.add(i); //keep track of the active widget - int containerCountsave = widgetManager.widgets.get(i).currentContainer; - //println("Widget " + i + " is in Container " + containerCountsave); - saveWidgetLayout.setInt("Widget_"+i, containerCountsave); - } else if (!widgetManager.widgets.get(i).getIsActive()) { //If a widget is not active... - saveWidgetLayout.remove("Widget_"+i); //remove non-active widget from JSON - //println("widget"+i+" is not active"); + + // Save active widgets and their container positions + for (int i = 0; i < widgetManager.widgets.size(); i++) { + Widget widget = widgetManager.widgets.get(i); + if (widget.getIsActive()) { + numActiveWidgets++; + widgetLayout.setInt("Widget_" + i, widget.currentContainer); } } + println("SessionSettings: " + numActiveWidgets + " active widgets saved!"); - saveSettingsJSONData.setJSONObject(WIDGET_CONTAINER_SETTINGS_KEY, saveWidgetLayout); - - // Settings for all widgets - JSONObject saveWidgetSettings = parseJSONObject(widgetManager.getWidgetSettingsAsJson()); - saveSettingsJSONData.setJSONObject(WIDGET_SETTINGS_KEY, saveWidgetSettings); - - //Let's save the JSON array to a file! - saveJSONObject(saveSettingsJSONData, saveGUISettingsFileLocation); - - } - - void load(String loadGUISettingsFileLocation) throws Exception { - //Load all saved User Settings from a JSON file if it exists - loadSettingsJSONData = loadJSONObject(loadGUISettingsFileLocation); - - verbosePrint(loadSettingsJSONData.toString()); + return widgetLayout; + } - //Check the number of channels saved to json first! - JSONObject loadGlobalSettings = loadSettingsJSONData.getJSONObject(GLOBAL_SETTINGS_KEY); - int numChanloaded = loadGlobalSettings.getInt(CHANNEL_COUNT_KEY); - //Print error if trying to load a different number of channels - if (numChanloaded != globalChannelCount) { - println("SessionSettings: Channels being loaded from " + loadGUISettingsFileLocation + " don't match channels being used!"); + /** + * Load settings from a file + */ + void load(String loadFilePath) throws Exception { + // Load and parse JSON data + loadSettingsJSONData = loadJSONObject(loadFilePath); + JSONObject globalSettings = loadSettingsJSONData.getJSONObject(KEY_GLOBAL); + + // Validate settings match current configuration + validateSettings(globalSettings); + + // Apply settings in order + currentLayout = globalSettings.getInt(KEY_LAYOUT); + applyDataSmoothingSettings(globalSettings); + applyNetworkingSettings(); + applyWidgetLayout(); + applyWidgetSettings(); + } + + /** + * Validate that loaded settings are compatible with current configuration + */ + private void validateSettings(JSONObject globalSettings) throws Exception { + // Check channel count match + int loadedChannels = globalSettings.getInt(KEY_CHANNELS); + if (loadedChannels != globalChannelCount) { chanNumError = true; - throw new Exception(); - } else { - chanNumError = false; + throw new Exception("Channel count mismatch"); } - //Check the Data Source integer next: Cyton = 0, Ganglion = 1, Playback = 2, Synthetic = 3 - int loadDatasource = loadGlobalSettings.getInt(DATA_SOURCE_KEY); - verbosePrint("SessionSettings: Data source loaded: " + loadDatasource + ". Current data source: " + eegDataSource); - //Print error if trying to load a different data source (ex. Live != Synthetic) - if (loadDatasource != eegDataSource) { - println("Data source being loaded from " + loadGUISettingsFileLocation + " doesn't match current data source."); + chanNumError = false; + + // Check data source match + int loadedDataSource = globalSettings.getInt(KEY_DATA_SOURCE); + if (loadedDataSource != eegDataSource) { dataSourceError = true; - throw new Exception(); - } else { - dataSourceError = false; + throw new Exception("Data source mismatch"); } - - if (currentBoard instanceof SmoothingCapableBoard) { - Boolean loadDataSmoothingSetting = loadGlobalSettings.getBoolean(DATA_SMOOTHING_KEY); - ((SmoothingCapableBoard)currentBoard).setSmoothingActive(loadDataSmoothingSetting); + dataSourceError = false; + } + + /** + * Apply data smoothing settings if available + */ + private void applyDataSmoothingSettings(JSONObject globalSettings) { + if (currentBoard instanceof SmoothingCapableBoard && + globalSettings.hasKey(KEY_SMOOTHING)) { + + ((SmoothingCapableBoard)currentBoard).setSmoothingActive( + globalSettings.getBoolean(KEY_SMOOTHING)); topNav.updateSmoothingButtonText(); } - - // Layout Settings - currentLayout = loadGlobalSettings.getInt(WIDGET_LAYOUT_KEY); - - // Networking Settings - JSONObject networkingSettingsJson = loadSettingsJSONData.getJSONObject(NETWORKING_KEY); - dataProcessing.networkingSettings.loadJson(networkingSettingsJson.toString()); - - // Widget Layout Settings - JSONObject widgetContainerSettings = loadSettingsJSONData.getJSONObject(WIDGET_CONTAINER_SETTINGS_KEY); - //Apply Layout directly before loading and applying widgets to containers + } + + /** + * Apply networking settings + */ + private void applyNetworkingSettings() { + dataProcessing.networkingSettings.loadJson( + loadSettingsJSONData.getJSONObject(KEY_NETWORKING).toString()); + } + + /** + * Apply widget layout and container positions + */ + private void applyWidgetLayout() { + // Set layout first widgetManager.setNewContainerLayout(currentLayout); - verbosePrint("SessionSettings: Layout " + currentLayout + " Loaded!"); - int numLoadedWidgets = widgetContainerSettings.size(); - - //int numActiveWidgets = 0; //reset the counter - for (int i = 0; i < widgetManager.widgets.size(); i++) { //increment through all widgets - if (widgetManager.widgets.get(i).getIsActive()) { //If a widget is active... - widgetManager.widgets.get(i).setIsActive(false); - } + + // Deactivate all widgets initially + for (Widget widget : widgetManager.widgets) { + widget.setIsActive(false); } - - //Store the Widget number keys from JSON to a string array - String[] loadedWidgetsArray = (String[]) widgetContainerSettings.keys().toArray(new String[widgetContainerSettings.size()]); - //printArray(loadedWidgetsArray); - int widgetToActivate = 0; - for (int w = 0; w < numLoadedWidgets; w++) { - String [] loadWidgetNameNumber = split(loadedWidgetsArray[w], '_'); - //Store the value of the widget to be activated - widgetToActivate = Integer.valueOf(loadWidgetNameNumber[1]); - //Load the container for the current widget[w] - int containerToApply = widgetContainerSettings.getInt(loadedWidgetsArray[w]); - - widgetManager.widgets.get(widgetToActivate).setIsActive(true);//activate the new widget - widgetManager.widgets.get(widgetToActivate).setContainer(containerToApply);//map it to the container that was loaded! - println("SessionSettings: Applied Widget " + widgetToActivate + " to Container " + containerToApply); + + // Get widget container settings + JSONObject containerSettings = loadSettingsJSONData.getJSONObject(KEY_CONTAINERS); + + // Activate widgets and set containers + // Fix: Properly handle keys as a Set from containerSettings.keys() + for (Object keyObj : containerSettings.keys()) { + String key = keyObj.toString(); + String[] keyParts = split(key, '_'); + int widgetIndex = Integer.valueOf(keyParts[1]); + int containerIndex = containerSettings.getInt(key); + + Widget widget = widgetManager.widgets.get(widgetIndex); + widget.setIsActive(true); + widget.setContainer(containerIndex); } + } + + /** + * Apply individual widget settings + */ + private void applyWidgetSettings() { + widgetManager.loadWidgetSettingsFromJson( + loadSettingsJSONData.getJSONObject(KEY_WIDGET_SETTINGS).toString()); + } - JSONObject widgetSettings = loadSettingsJSONData.getJSONObject(WIDGET_SETTINGS_KEY); - widgetManager.loadWidgetSettingsFromJson(widgetSettings.toString()); - - //Load and apply all of the settings that are in dropdown menus. It's a bit much, so it has it's own function below. - //loadApplyWidgetDropdownText(); - - //Apply Time Series Settings Last!!! - //loadApplyTimeSeriesSettings(); + /** + * Get the appropriate settings file path based on mode and configuration + */ + String getPath(String mode, int dataSource, int channelCount) { + // Determine which settings file to use + int modeIndex = mode.equals("Default") ? FILE_DEFAULT : FILE_USER; + int fileIndex; + + if (dataSource == DATASOURCE_CYTON) { + fileIndex = (channelCount == CYTON_CHANNEL_COUNT) ? 0 : 1; + } else if (dataSource == DATASOURCE_GANGLION) { + fileIndex = 2; + } else if (dataSource == DATASOURCE_PLAYBACKFILE) { + fileIndex = 3; + } else if (dataSource == DATASOURCE_SYNTHETIC) { + if (channelCount == GANGLION_CHANNEL_COUNT) { + fileIndex = 4; + } else if (channelCount == CYTON_CHANNEL_COUNT) { + fileIndex = 5; + } else { + fileIndex = 6; + } + } else { + return "Error"; + } + + return directoryManager.getSettingsPath() + SETTING_FILES[fileIndex][modeIndex]; } /** - * @description Used in TopNav when user clicks ClearSettings->AreYouSure->Yes - * @params none - * Output Success message to bottom of GUI when done - */ + * Clear all settings files + */ void clearAll() { - for (File file: new File(directoryManager.getSettingsPath()).listFiles()) - if (!file.isDirectory()) + // Delete all settings files + for (File file : new File(directoryManager.getSettingsPath()).listFiles()) { + if (!file.isDirectory()) { file.delete(); + } + } + + // Clear playback history controlPanel.recentPlaybackBox.rpb_cp5.get(ScrollableList.class, "recentPlaybackFilesCP").clear(); controlPanel.recentPlaybackBox.shortFileNames.clear(); controlPanel.recentPlaybackBox.longFilePaths.clear(); + outputSuccess("All settings have been cleared!"); } /** - * @description Used in System Init, TopNav, and Interactivity - * @params mode="User"or"Default", dataSource, and number of channels - * @returns {String} - filePath or Error if mode not specified correctly - */ - String getPath(String _mode, int dataSource, int _channelCount) { - String filePath = directoryManager.getSettingsPath(); - String[] fileNames = new String[7]; - if (_mode.equals("Default")) { - fileNames = DEFAULT_SETTINGS_FILES; - } else if (_mode.equals("User")) { - fileNames = USER_SETTINGS_FILES; - } else { - filePath = "Error"; - } - if (!filePath.equals("Error")) { - if (dataSource == DATASOURCE_CYTON) { - filePath += (_channelCount == CYTON_CHANNEL_COUNT) ? - fileNames[0] : - fileNames[1]; - } else if (dataSource == DATASOURCE_GANGLION) { - filePath += fileNames[2]; - } else if (dataSource == DATASOURCE_PLAYBACKFILE) { - filePath += fileNames[3]; - } else if (dataSource == DATASOURCE_SYNTHETIC) { - if (_channelCount == GANGLION_CHANNEL_COUNT) { - filePath += fileNames[4]; - } else if (_channelCount == CYTON_CHANNEL_COUNT) { - filePath += fileNames[5]; - } else { - filePath += fileNames[6]; - } - } - } - return filePath; - } - + * Handle key press to load settings + */ void loadKeyPressed() { loadErrorTimerStart = millis(); - String settingsFileToLoad = getPath("User", eegDataSource, globalChannelCount); + String settingsFile = getPath("User", eegDataSource, globalChannelCount); + try { - load(settingsFileToLoad); + load(settingsFile); errorUserSettingsNotFound = false; + outputSuccess("Settings Loaded!"); } catch (Exception e) { - //println(e.getMessage()); - e.printStackTrace(); - println(settingsFileToLoad + " not found or other error. Save settings with keyboard 'n' or using dropdown menu."); errorUserSettingsNotFound = true; + handleLoadError(settingsFile); } - //Output message when Loading settings is complete - String err = null; - if (chanNumError == false && dataSourceError == false && errorUserSettingsNotFound == false && loadErrorCytonEvent == false) { - outputSuccess("Settings Loaded!"); - } else if (chanNumError) { - err = "Invalid number of channels"; + } + + /** + * Handle errors when loading settings + */ + private void handleLoadError(String settingsFile) { + if (chanNumError) { + outputError("Settings Error: Channel Number Mismatch"); } else if (dataSourceError) { - err = "Invalid data source"; - } else if (errorUserSettingsNotFound) { - err = settingsFileToLoad + " not found."; - } - - //Only try to delete file for SettingsNotFound/Broken settings - if (err != null && (!chanNumError && !dataSourceError)) { - println("Load Settings Error: " + err); - File f = new File(settingsFileToLoad); - if (f.exists()) { - if (f.delete()) { - outputError("Found old/broken GUI settings. Please reconfigure the GUI and save new settings."); - } else { - outputError("SessionSettings: Error deleting old/broken settings file..."); - } + outputError("Settings Error: Data Source Mismatch"); + } else { + File f = new File(settingsFile); + if (f.exists() && f.delete()) { + outputError("Found old/broken GUI settings. Please reconfigure the GUI and save new settings."); + } else if (f.exists()) { + outputError("Error deleting old/broken settings file."); } } } + /** + * Handle save button press + */ void saveButtonPressed() { if (saveDialogName == null) { - File fileToSave = dataFile(sessionSettings.getPath("User", eegDataSource, globalChannelCount)); - FileChooser chooser = new FileChooser( - FileChooserMode.SAVE, - "saveConfigFile", - fileToSave, - "Save settings to file"); + // Open file chooser dialog + File fileToSave = dataFile(getPath("User", eegDataSource, globalChannelCount)); + new FileChooser(FileChooserMode.SAVE, "saveConfigFile", fileToSave, + "Save settings to file"); } else { - println("saveSettingsFileName = " + saveDialogName); saveDialogName = null; } } + /** + * Handle load button press + */ void loadButtonPressed() { - //Select file to load from dialog box if (loadDialogName == null) { - FileChooser chooser = new FileChooser( - FileChooserMode.LOAD, - "loadConfigFile", - new File(directoryManager.getGuiDataPath() + "Settings"), - "Select a settings file to load"); + // Open file chooser dialog + new FileChooser(FileChooserMode.LOAD, "loadConfigFile", + new File(directoryManager.getGuiDataPath() + "Settings"), + "Select a settings file to load"); saveDialogName = null; } else { - println("loadSettingsFileName = " + loadDialogName); loadDialogName = null; } } + /** + * Reset to default settings + */ void defaultButtonPressed() { - //Revert GUI to default settings that were flashed on system start! - String defaultSettingsFileToLoad = getPath("Default", eegDataSource, globalChannelCount); + String defaultFile = getPath("Default", eegDataSource, globalChannelCount); try { - //Load all saved User Settings from a JSON file to see if it exists - JSONObject loadDefaultSettingsJSONData = loadJSONObject(defaultSettingsFileToLoad); - this.load(defaultSettingsFileToLoad); + // Check if default settings exist and load them + loadJSONObject(defaultFile); + load(defaultFile); outputSuccess("Default Settings Loaded!"); } catch (Exception e) { outputError("Default Settings Error: Valid Default Settings will be saved next system start."); - File f = new File(defaultSettingsFileToLoad); - if (f.exists()) { - if (f.delete()) { - println("SessionSettings: Old/Broken Default Settings file succesfully deleted."); - } else { - println("SessionSettings: Error deleting Default Settings file..."); - } + File f = new File(defaultFile); + if (f.exists() && !f.delete()) { + println("SessionSettings: Error deleting Default Settings file..."); } } } + /** + * Auto-load settings at startup + */ public void autoLoadSessionSettings() { loadKeyPressed(); } - } -////////////////////////////////////////// -// Global Functions // -// Called by Buttons with the same name // -////////////////////////////////////////// -// Select file to save custom settings using dropdown in TopNav.pde +/** + * Process file selection for saving settings + */ void saveConfigFile(File selection) { if (selection == null) { - println("SessionSettings: saveConfigFile: Window was closed or the user hit cancel."); - } else { - println("SessionSettings: saveConfigFile: User selected " + selection.getAbsolutePath()); - sessionSettings.saveDialogName = selection.getAbsolutePath(); - sessionSettings.save(sessionSettings.saveDialogName); //save current settings to JSON file in SavedData - outputSuccess("Settings Saved! Using Expert Mode, you can load these settings using 'N' key. Click \"Default\" to revert to factory settings."); //print success message to screen - sessionSettings.saveDialogName = null; //reset this variable for future use + return; } + + sessionSettings.save(selection.getAbsolutePath()); + outputSuccess("Settings Saved! Using Expert Mode, you can load these settings using 'N' key."); + sessionSettings.saveDialogName = null; } -// Select file to load custom settings using dropdown in TopNav.pde + +/** + * Process file selection for loading settings + */ void loadConfigFile(File selection) { if (selection == null) { - println("SessionSettings: loadConfigFile: Window was closed or the user hit cancel."); + return; + } + + try { + sessionSettings.load(selection.getAbsolutePath()); + if (!sessionSettings.chanNumError && !sessionSettings.dataSourceError && + !sessionSettings.loadErrorCytonEvent) { + outputSuccess("Settings Loaded!"); + } + } catch (Exception e) { + handleLoadConfigError(selection); + } + + sessionSettings.loadDialogName = null; +} + +/** + * Handle errors in loadConfigFile + */ +void handleLoadConfigError(File selection) { + if (sessionSettings.chanNumError) { + outputError("Settings Error: Channel Number Mismatch Detected"); + } else if (sessionSettings.dataSourceError) { + outputError("Settings Error: Data Source Mismatch Detected"); } else { - println("SessionSettings: loadConfigFile: User selected " + selection.getAbsolutePath()); - //output("You have selected \"" + selection.getAbsolutePath() + "\" to Load custom settings."); - sessionSettings.loadDialogName = selection.getAbsolutePath(); - try { - sessionSettings.load(sessionSettings.loadDialogName); //load settings from JSON file in /data/ - //Output success message when Loading settings is complete without errors - if (sessionSettings.chanNumError == false - && sessionSettings.dataSourceError == false - && sessionSettings.loadErrorCytonEvent == false) { - outputSuccess("Settings Loaded!"); - } - } catch (Exception e) { - println("SessionSettings: Incompatible settings file or other error"); - if (sessionSettings.chanNumError == true) { - outputError("Settings Error: Channel Number Mismatch Detected"); - } else if (sessionSettings.dataSourceError == true) { - outputError("Settings Error: Data Source Mismatch Detected"); - } else { - outputError("Error trying to load settings file, possibly from previous GUI. Removing old settings."); - if (selection.exists()) selection.delete(); - } + outputError("Error trying to load settings file, possibly from previous GUI."); + if (selection.exists()) { + selection.delete(); } - sessionSettings.loadDialogName = null; //reset this variable for future use } } \ No newline at end of file From f1053e038f329ba5431b7dd7e7988d50a65aeba8 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 24 Apr 2025 13:27:51 -0500 Subject: [PATCH 19/29] Add WidgetWithSettings to Accelerometer, AnalogRead, FFT, and BandPower widgets --- OpenBCI_GUI/W_Accelerometer.pde | 32 ++++++--- OpenBCI_GUI/W_AnalogRead.pde | 54 +++++++++++---- OpenBCI_GUI/W_BandPower.pde | 101 ++++++++++++++------------- OpenBCI_GUI/W_FFT.pde | 117 ++++++++++++++++++++++---------- OpenBCI_GUI/W_TimeSeries.pde | 14 ++-- 5 files changed, 204 insertions(+), 114 deletions(-) diff --git a/OpenBCI_GUI/W_Accelerometer.pde b/OpenBCI_GUI/W_Accelerometer.pde index 23bb484ac..1fc78c17d 100644 --- a/OpenBCI_GUI/W_Accelerometer.pde +++ b/OpenBCI_GUI/W_Accelerometer.pde @@ -11,7 +11,7 @@ // //////////////////////////////////////////////////// -class W_Accelerometer extends Widget { +class W_Accelerometer extends WidgetWithSettings { //To see all core variables/methods of the Widget class, refer to Widget.pde color graphStroke = color(210); color graphBG = color(245); @@ -49,11 +49,7 @@ class W_Accelerometer extends Widget { widgetTitle = "Accelerometer"; accelBoard = (AccelerometerCapableBoard)currentBoard; - - //Make dropdowns - addDropdown("accelerometerVerticalScaleDropdown", "Vert Scale", EnumHelper.getEnumStrings(AccelerometerVerticalScale.class), verticalScale.getIndex()); - addDropdown("accelerometerHorizontalScaleDropdown", "Window", EnumHelper.getEnumStrings(AccelerometerHorizontalScale.class), horizontalScale.getIndex()); - + setGraphDimensions(); polarYMaxMin = adjustYMaxMinBasedOnSource(); @@ -68,6 +64,20 @@ class W_Accelerometer extends Widget { createAccelModeButton("accelModeButton", "Turn Accel. Off", (int)(x + 1), (int)(y0 + NAV_HEIGHT + 1), 120, NAV_HEIGHT - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); } + @Override void initWidgetSettings() { + super.initWidgetSettings(); + widgetSettings.set(AccelerometerVerticalScale.class, AccelerometerVerticalScale.AUTO) + .set(AccelerometerHorizontalScale.class, AccelerometerHorizontalScale.FIVE_SEC) + .saveDefaults(); + initDropdown(AccelerometerVerticalScale.class, "accelerometerVerticalScaleDropdown", "Vert Scale"); + initDropdown(AccelerometerHorizontalScale.class, "accelerometerHorizontalScaleDropdown", "Window"); + } + + @Override void applySettings() { + updateDropdownLabel(AccelerometerVerticalScale.class, "accelerometerVerticalScaleDropdown"); + updateDropdownLabel(AccelerometerHorizontalScale.class, "accelerometerHorizontalScaleDropdown"); + } + float adjustYMaxMinBasedOnSource() { float _yMaxMin; if (eegDataSource == DATASOURCE_CYTON) { @@ -265,13 +275,15 @@ class W_Accelerometer extends Widget { } public void setVerticalScale(int n) { - verticalScale = AccelerometerVerticalScale.values()[n]; - accelerometerBar.adjustVertScale(verticalScale.getValue()); + widgetSettings.setByIndex(AccelerometerVerticalScale.class, n); + int verticalScaleValue = widgetSettings.get(AccelerometerVerticalScale.class).getValue(); + accelerometerBar.adjustVertScale(verticalScaleValue); } public void setHorizontalScale(int n) { - horizontalScale = AccelerometerHorizontalScale.values()[n]; - accelerometerBar.adjustTimeAxis(horizontalScale.getValue()); + widgetSettings.setByIndex(AccelerometerHorizontalScale.class, n); + int horizontalScaleValue = widgetSettings.get(AccelerometerHorizontalScale.class).getValue(); + accelerometerBar.adjustTimeAxis(horizontalScaleValue); } }; diff --git a/OpenBCI_GUI/W_AnalogRead.pde b/OpenBCI_GUI/W_AnalogRead.pde index 48119fda0..d2b6685af 100644 --- a/OpenBCI_GUI/W_AnalogRead.pde +++ b/OpenBCI_GUI/W_AnalogRead.pde @@ -8,7 +8,7 @@ // // //////////////////////////////////////////////////////////////////////// -class W_AnalogRead extends Widget { +class W_AnalogRead extends WidgetWithSettings { private float arPadding; // values for actual time series chart (rectangle encompassing all analogReadBars) private float ar_x, ar_y, ar_h, ar_w; @@ -18,8 +18,6 @@ class W_AnalogRead extends Widget { private final int NUM_ANALOG_READ_BARS = 3; private AnalogReadBar[] analogReadBars; - private AnalogReadHorizontalScale horizontalScale = AnalogReadHorizontalScale.FIVE_SEC; - private AnalogReadVerticalScale verticalScale = AnalogReadVerticalScale.ONE_THOUSAND_FIFTY; private boolean allowSpillover = false; @@ -33,9 +31,6 @@ class W_AnalogRead extends Widget { analogBoard = (AnalogCapableBoard)currentBoard; - addDropdown("analogReadVerticalScaleDropdown", "Vert Scale", EnumHelper.getEnumStrings(AnalogReadVerticalScale.class), verticalScale.getIndex()); - addDropdown("analogReadHorizontalScaleDropdown", "Window", EnumHelper.getEnumStrings(AnalogReadHorizontalScale.class), horizontalScale.getIndex()); - plotBottomWell = 45.0; //this appears to be an arbitrary vertical space adds GPlot leaves at bottom, I derived it through trial and error arPadding = 10.0; ar_x = float(x) + arPadding; @@ -52,13 +47,32 @@ class W_AnalogRead extends Widget { AnalogReadBar tempBar = new AnalogReadBar(ourApplet, i+5, int(ar_x), analogReadBarY, int(ar_w), analogReadBarHeight); //int _channelNumber, int _x, int _y, int _w, int _h analogReadBars[i] = tempBar; } - - setVerticalScale(verticalScale.getIndex()); - setHorizontalScale(horizontalScale.getIndex()); + + int verticalScaleValue = widgetSettings.get(AnalogReadVerticalScale.class).getValue(); + int horizontalScaleValue = widgetSettings.get(AnalogReadHorizontalScale.class).getValue(); + applyVerticalScale(verticalScaleValue); + applyHorizontalScale(horizontalScaleValue); createAnalogModeButton("analogModeButton", "Turn Analog Read On", (int)(x0 + 1), (int)(y0 + NAV_HEIGHT + 1), 128, NAV_HEIGHT - 3, p5, 12, colorNotPressed, OPENBCI_DARKBLUE); } + @Override + protected void initWidgetSettings() { + super.initWidgetSettings(); + widgetSettings.set(AnalogReadVerticalScale.class, AnalogReadVerticalScale.ONE_THOUSAND_FIFTY) + .set(AnalogReadHorizontalScale.class, AnalogReadHorizontalScale.FIVE_SEC) + .saveDefaults(); + + initDropdown(AnalogReadVerticalScale.class, "analogReadVerticalScaleDropdown", "Vert Scale"); + initDropdown(AnalogReadHorizontalScale.class, "analogReadHorizontalScaleDropdown", "Window"); + } + + @Override + protected void applySettings() { + updateDropdownLabel(AnalogReadVerticalScale.class, "analogReadVerticalScaleDropdown"); + updateDropdownLabel(AnalogReadHorizontalScale.class, "analogReadHorizontalScaleDropdown"); + } + public void update() { super.update(); @@ -159,16 +173,26 @@ class W_AnalogRead extends Widget { } public void setVerticalScale(int n) { - verticalScale = verticalScale.values()[n]; - for(int i = 0; i < analogReadBars.length; i++) { - analogReadBars[i].adjustVertScale(verticalScale.getValue()); - } + widgetSettings.setByIndex(AnalogReadVerticalScale.class, n); + int verticalScaleValue = widgetSettings.get(AnalogReadVerticalScale.class).getValue(); + applyVerticalScale(verticalScaleValue); } public void setHorizontalScale(int n) { - horizontalScale = horizontalScale.values()[n]; + widgetSettings.setByIndex(AnalogReadHorizontalScale.class, n); + int horizontalScaleValue = widgetSettings.get(AnalogReadHorizontalScale.class).getValue(); + applyHorizontalScale(horizontalScaleValue); + } + + private void applyVerticalScale(int value) { + for(int i = 0; i < analogReadBars.length; i++) { + analogReadBars[i].adjustVertScale(value); + } + } + + private void applyHorizontalScale(int value) { for(int i = 0; i < analogReadBars.length; i++) { - analogReadBars[i].adjustTimeAxis(horizontalScale.getValue()); + analogReadBars[i].adjustTimeAxis(value); } } }; diff --git a/OpenBCI_GUI/W_BandPower.pde b/OpenBCI_GUI/W_BandPower.pde index ccae0521e..8040e0f9e 100644 --- a/OpenBCI_GUI/W_BandPower.pde +++ b/OpenBCI_GUI/W_BandPower.pde @@ -14,7 +14,7 @@ // // //////////////////////////////////////////////////////////////////////////////////////////////////////// -class W_BandPower extends Widget { +class W_BandPower extends WidgetWithSettings { // indexes private final int DELTA = 0; // 1-4 Hz private final int THETA = 1; // 4-8 Hz @@ -30,11 +30,7 @@ class W_BandPower extends Widget { public ExGChannelSelect bpChanSelect; private boolean prevChanSelectIsVisible = false; - private List cp5ElementsToCheck = new ArrayList(); - - BPAutoClean autoClean = BPAutoClean.OFF; - BPAutoCleanThreshold autoCleanThreshold = BPAutoCleanThreshold.FIFTY; - BPAutoCleanTimer autoCleanTimer = BPAutoCleanTimer.THREE_SECONDS; + private List cp5ElementsToCheck; int[] autoCleanTimers; boolean[] previousThresholdCrossed; @@ -46,28 +42,6 @@ class W_BandPower extends Widget { autoCleanTimers = new int[currentBoard.getNumEXGChannels()]; previousThresholdCrossed = new boolean[currentBoard.getNumEXGChannels()]; - //Add channel select dropdown to this widget - bpChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); - bpChanSelect.activateAllButtons(); - - cp5ElementsToCheck.addAll(bpChanSelect.getCp5ElementsForOverlapCheck()); - - List autoCleanList = EnumHelper.getEnumStrings(BPAutoClean.class); - List autoCleanThresholdList = EnumHelper.getEnumStrings(BPAutoCleanThreshold.class); - List autoCleanTimerList = EnumHelper.getEnumStrings(BPAutoCleanTimer.class); - List smoothingFactorList = EnumHelper.getEnumStrings(FFTSmoothingFactor.class); - List filteredEnumList = EnumHelper.getEnumStrings(FFTFilteredEnum.class); - - //Add settings dropdowns - addDropdown("bandPowerAutoCleanDropdown", "AutoClean", autoCleanList, autoClean.getIndex()); - addDropdown("bandPowerAutoCleanThresholdDropdown", "Threshold", autoCleanThresholdList, autoCleanThreshold.getIndex()); - addDropdown("bandPowerAutoCleanTimerDropdown", "Timer", autoCleanTimerList, autoCleanTimer.getIndex()); - //These two dropdowns also have to mirror the settings in the FFT widget - FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); - FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); - addDropdown("bandPowerSmoothingDropdown", "Smooth", smoothingFactorList, smoothingFactor.getIndex()); - addDropdown("bandPowerDataFilteringDropdown", "Filtered?", filteredEnumList, filteredEnum.getIndex()); - // Setup for the BandPower plot bp_plot = new GPlot(ourApplet, x, y-NAV_HEIGHT, w, h+NAV_HEIGHT); // bp_plot.setPos(x, y+NAV_HEIGHT); @@ -110,6 +84,46 @@ class W_BandPower extends Widget { bp_plot.getHistogram().setFontColor(OPENBCI_DARKBLUE); } + @Override + protected void initWidgetSettings() { + super.initWidgetSettings(); + widgetSettings.set(BPAutoClean.class, BPAutoClean.OFF) + .set(BPAutoCleanThreshold.class, BPAutoCleanThreshold.FIFTY) + .set(BPAutoCleanTimer.class, BPAutoCleanTimer.THREE_SECONDS) + .set(FFTSmoothingFactor.class, globalFFTSettings.getSmoothingFactor()) + .set(FFTFilteredEnum.class, globalFFTSettings.getFilteredEnum()); + + initDropdown(BPAutoClean.class, "bandPowerAutoCleanDropdown", "Auto Clean"); + initDropdown(BPAutoCleanThreshold.class, "bandPowerAutoCleanThresholdDropdown", "Threshold"); + initDropdown(BPAutoCleanTimer.class, "bandPowerAutoCleanTimerDropdown", "Timer"); + initDropdown(FFTSmoothingFactor.class, "bandPowerSmoothingDropdown", "Smooth"); + initDropdown(FFTFilteredEnum.class, "bandPowerDataFilteringDropdown", "Filtered?"); + + bpChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); + bpChanSelect.activateAllButtons(); + cp5ElementsToCheck = new ArrayList(); + cp5ElementsToCheck.addAll(bpChanSelect.getCp5ElementsForOverlapCheck()); + saveActiveChannels(bpChanSelect.getActiveChannels()); + widgetSettings.saveDefaults(); + } + + @Override + protected void applySettings() { + updateDropdownLabel(BPAutoClean.class, "bandPowerAutoCleanDropdown"); + updateDropdownLabel(BPAutoCleanThreshold.class, "bandPowerAutoCleanThresholdDropdown"); + updateDropdownLabel(BPAutoCleanTimer.class, "bandPowerAutoCleanTimerDropdown"); + updateDropdownLabel(FFTSmoothingFactor.class, "bandPowerSmoothingDropdown"); + updateDropdownLabel(FFTFilteredEnum.class, "bandPowerDataFilteringDropdown"); + applyActiveChannels(bpChanSelect); + } + + @Override + protected void updateChannelSettings() { + if (bpChanSelect != null) { + saveActiveChannels(bpChanSelect.getActiveChannels()); + } + } + public void update() { super.update(); @@ -189,6 +203,9 @@ class W_BandPower extends Widget { } private void autoCleanByEnableDisableChannels() { + BPAutoClean autoClean = widgetSettings.get(BPAutoClean.class); + BPAutoCleanThreshold autoCleanThreshold = widgetSettings.get(BPAutoCleanThreshold.class); + BPAutoCleanTimer autoCleanTimer = widgetSettings.get(BPAutoCleanTimer.class); if (autoClean == BPAutoClean.OFF) { return; } @@ -215,30 +232,18 @@ class W_BandPower extends Widget { } } - public BPAutoClean getAutoClean() { - return autoClean; - } - - public BPAutoCleanThreshold getAutoCleanThreshold() { - return autoCleanThreshold; - } - - public BPAutoCleanTimer getAutoCleanTimer() { - return autoCleanTimer; - } - public void setAutoClean(int n) { - autoClean = autoClean.values()[n]; + widgetSettings.setByIndex(BPAutoClean.class, n); Arrays.fill(previousThresholdCrossed, false); Arrays.fill(autoCleanTimers, 0); } public void setAutoCleanThreshold(int n) { - autoCleanThreshold = autoCleanThreshold.values()[n]; + widgetSettings.setByIndex(BPAutoCleanThreshold.class, n); } public void setAutoCleanTimer(int n) { - autoCleanTimer = autoCleanTimer.values()[n]; + widgetSettings.setByIndex(BPAutoCleanTimer.class, n); } //Called in DataProcessing.pde to update data even if widget is closed @@ -261,13 +266,13 @@ class W_BandPower extends Widget { } public void setSmoothingDropdownFrontend(FFTSmoothingFactor _smoothingFactor) { - String s = _smoothingFactor.getString(); - cp5_widget.getController("bandPowerSmoothingDropdown").getCaptionLabel().setText(s); + widgetSettings.set(FFTSmoothingFactor.class, _smoothingFactor); + updateDropdownLabel(FFTSmoothingFactor.class, "bandPowerSmoothingDropdown"); } public void setFilteringDropdownFrontend(FFTFilteredEnum _filteredEnum) { - String s = _filteredEnum.getString(); - cp5_widget.getController("bandPowerDataFilteringDropdown").getCaptionLabel().setText(s); + widgetSettings.set(FFTFilteredEnum.class, _filteredEnum); + updateDropdownLabel(FFTFilteredEnum.class, "bandPowerDataFilteringDropdown"); } }; @@ -286,11 +291,13 @@ public void bandPowerAutoCleanTimerDropdown(int n) { public void bandPowerSmoothingDropdown(int n) { globalFFTSettings.setSmoothingFactor(FFTSmoothingFactor.values()[n]); FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); + ((W_BandPower) widgetManager.getWidget("W_BandPower")).setSmoothingDropdownFrontend(smoothingFactor); ((W_Fft) widgetManager.getWidget("W_Fft")).setSmoothingDropdownFrontend(smoothingFactor); } public void bandPowerDataFilteringDropdown(int n) { globalFFTSettings.setFilteredEnum(FFTFilteredEnum.values()[n]); FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); + ((W_BandPower) widgetManager.getWidget("W_BandPower")).setFilteringDropdownFrontend(filteredEnum); ((W_Fft) widgetManager.getWidget("W_Fft")).setFilteringDropdownFrontend(filteredEnum); } \ No newline at end of file diff --git a/OpenBCI_GUI/W_FFT.pde b/OpenBCI_GUI/W_FFT.pde index 2fefc6996..846b445b9 100644 --- a/OpenBCI_GUI/W_FFT.pde +++ b/OpenBCI_GUI/W_FFT.pde @@ -12,49 +12,77 @@ // /////////////////////////////////////////////////// -class W_Fft extends Widget { +class W_Fft extends WidgetWithSettings { public ExGChannelSelect fftChanSelect; private boolean prevChanSelectIsVisible = false; private GPlot fftPlot; //create an fft plot for each active channel private GPointsArray[] fftGplotPoints; - private FFTMaxFrequency maxFrequency = FFTMaxFrequency.MAX_60; - private FFTVerticalScale verticalScale = FFTVerticalScale.SCALE_100; - private FFTLogLin logLin = FFTLogLin.LOG; + private int fftFrequencyLimit; - private final int FFT_FREQUENCY_LIMIT = int(1.0 * maxFrequency.getHighestFrequency() * (getNumFFTPoints() / currentBoard.getSampleRate())); - - List cp5ElementsToCheck = new ArrayList(); + private List cp5ElementsToCheck; W_Fft() { super(); widgetTitle = "FFT Plot"; + fftGplotPoints = new GPointsArray[globalChannelCount]; + initializeFFTPlot(); + } + + @Override + protected void initWidgetSettings() { + super.initWidgetSettings(); + widgetSettings.set(FFTMaxFrequency.class, FFTMaxFrequency.MAX_60) + .set(FFTVerticalScale.class, FFTVerticalScale.SCALE_100) + .set(FFTLogLin.class, FFTLogLin.LOG) + .set(FFTSmoothingFactor.class, globalFFTSettings.getSmoothingFactor()) + .set(FFTFilteredEnum.class, globalFFTSettings.getFilteredEnum()); + + initDropdown(FFTMaxFrequency.class, "fftMaxFrequencyDropdown", "Max Hz"); + initDropdown(FFTVerticalScale.class, "fftVerticalScaleDropdown", "Max uV"); + initDropdown(FFTLogLin.class, "fftLogLinDropdown", "Log/Lin"); + initDropdown(FFTSmoothingFactor.class, "fftSmoothingDropdown", "Smooth"); + initDropdown(FFTFilteredEnum.class, "fftFilteringDropdown", "Filters?"); + fftChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); fftChanSelect.activateAllButtons(); - + cp5ElementsToCheck = new ArrayList(); cp5ElementsToCheck.addAll(fftChanSelect.getCp5ElementsForOverlapCheck()); + saveActiveChannels(fftChanSelect.getActiveChannels()); + widgetSettings.saveDefaults(); - List maxFrequencyList = EnumHelper.getEnumStrings(FFTMaxFrequency.class); - List verticalScaleList = EnumHelper.getEnumStrings(FFTVerticalScale.class); - List logLinList = EnumHelper.getEnumStrings(FFTLogLin.class); - List smoothingList = EnumHelper.getEnumStrings(FFTSmoothingFactor.class); - List filteredEnumList = EnumHelper.getEnumStrings(FFTFilteredEnum.class); + int maxFrequencyHighestValue = ((FFTMaxFrequency) widgetSettings.get(FFTMaxFrequency.class)).getHighestFrequency(); + fftFrequencyLimit = int(1.0 * maxFrequencyHighestValue * (getNumFFTPoints() / currentBoard.getSampleRate())); + } - addDropdown("fftMaxFrequencyDropdown", "Max Hz", maxFrequencyList, maxFrequency.getIndex()); - addDropdown("fftVerticalScaleDropdown", "Max uV", verticalScaleList, verticalScale.getIndex()); - addDropdown("fftLogLinDropdown", "Log/Lin", logLinList, logLin.getIndex()); - FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); - FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); - addDropdown("fftSmoothingDropdown", "Smooth", smoothingList, smoothingFactor.getIndex()); - addDropdown("fftFilteringDropdown", "Filters?", filteredEnumList, filteredEnum.getIndex()); + @Override + protected void applySettings() { + updateDropdownLabel(FFTMaxFrequency.class, "fftMaxFrequencyDropdown"); + updateDropdownLabel(FFTVerticalScale.class, "fftVerticalScaleDropdown"); + updateDropdownLabel(FFTLogLin.class, "fftLogLinDropdown"); + updateDropdownLabel(FFTSmoothingFactor.class, "fftSmoothingDropdown"); + updateDropdownLabel(FFTFilteredEnum.class, "fftFilteringDropdown"); + applyActiveChannels(fftChanSelect); + applyMaxFrequency(); + applyVerticalScale(); + setPlotLogScale(); + FFTSmoothingFactor smoothingFactor = widgetSettings.get(FFTSmoothingFactor.class); + FFTFilteredEnum filteredEnum = widgetSettings.get(FFTFilteredEnum.class); + globalFFTSettings.setSmoothingFactor(smoothingFactor); + globalFFTSettings.setFilteredEnum(filteredEnum); + } - fftGplotPoints = new GPointsArray[globalChannelCount]; - initializeFFTPlot(); + @Override + protected void updateChannelSettings() { + if (fftChanSelect != null) { + saveActiveChannels(fftChanSelect.getActiveChannels()); + } } - void initializeFFTPlot() { + + private void initializeFFTPlot() { //setup GPlot for FFT fftPlot = new GPlot(ourApplet, x, y-NAV_HEIGHT, w, h+NAV_HEIGHT); fftPlot.setAllFontProperties("Arial", 0, 14); @@ -63,10 +91,12 @@ class W_Fft extends Widget { fftPlot.setMar(60, 70, 40, 30); //{ bot=60, left=70, top=40, right=30 } by default setPlotLogScale(); - fftPlot.setYLim(0.1, verticalScale.getValue()); + int verticalScaleValue = widgetSettings.get(FFTVerticalScale.class).getValue(); + int maxFrequencyValue = widgetSettings.get(FFTMaxFrequency.class).getValue(); + fftPlot.setYLim(0.1, verticalScaleValue); int _nTicks = 10; fftPlot.getYAxis().setNTicks(_nTicks); //sets the number of axis divisions... - fftPlot.setXLim(0.1, maxFrequency.getValue()); + fftPlot.setXLim(0.1, maxFrequencyValue); fftPlot.getYAxis().setDrawTickLabels(true); fftPlot.setPointSize(2); fftPlot.setPointColor(0); @@ -79,12 +109,12 @@ class W_Fft extends Widget { //setup points of fft point arrays for (int i = 0; i < fftGplotPoints.length; i++) { - fftGplotPoints[i] = new GPointsArray(FFT_FREQUENCY_LIMIT); + fftGplotPoints[i] = new GPointsArray(fftFrequencyLimit); } //fill fft point arrays for (int i = 0; i < fftGplotPoints.length; i++) { //loop through each channel - for (int j = 0; j < FFT_FREQUENCY_LIMIT; j++) { + for (int j = 0; j < fftFrequencyLimit; j++) { GPoint temp = new GPoint(j, 0); fftGplotPoints[i].set(j, temp); } @@ -102,7 +132,7 @@ class W_Fft extends Widget { //update the points of the FFT channel arrays for all channels for (int i = 0; i < fftGplotPoints.length; i++) { - for (int j = 0; j < FFT_FREQUENCY_LIMIT + 2; j++) { //loop through frequency domain data, and store into points array + for (int j = 0; j < fftFrequencyLimit + 2; j++) { //loop through frequency domain data, and store into points array GPoint powerAtBin = new GPoint((1.0*sampleRate/fftPointCount)*j, fftBuff[i].getBand(j)); fftGplotPoints[i].set(j, powerAtBin); } @@ -182,22 +212,33 @@ class W_Fft extends Widget { } } + private void applyMaxFrequency() { + int maxFrequencyValue = widgetSettings.get(FFTMaxFrequency.class).getValue(); + fftPlot.setXLim(0.1, maxFrequencyValue); + } + + private void applyVerticalScale() { + int verticalScaleValue = widgetSettings.get(FFTVerticalScale.class).getValue(); + fftPlot.setYLim(0.1, verticalScaleValue); + } + public void setMaxFrequency(int n) { - maxFrequency = maxFrequency.values()[n]; - fftPlot.setXLim(0.1, maxFrequency.getValue()); + widgetSettings.setByIndex(FFTMaxFrequency.class, n); + applyMaxFrequency(); } public void setVerticalScale(int n) { - verticalScale = verticalScale.values()[n]; - fftPlot.setYLim(0.1, verticalScale.getValue()); + widgetSettings.setByIndex(FFTVerticalScale.class, n); + applyVerticalScale(); } public void setLogLin(int n) { - logLin = logLin.values()[n]; + widgetSettings.setByIndex(FFTLogLin.class, n); setPlotLogScale(); } private void setPlotLogScale() { + FFTLogLin logLin = widgetSettings.get(FFTLogLin.class); if (logLin == FFTLogLin.LOG) { fftPlot.setLogScale("y"); } else { @@ -206,13 +247,13 @@ class W_Fft extends Widget { } public void setSmoothingDropdownFrontend(FFTSmoothingFactor _smoothingFactor) { - String s = _smoothingFactor.getString(); - cp5_widget.getController("fftSmoothingDropdown").getCaptionLabel().setText(s); + widgetSettings.set(FFTSmoothingFactor.class, _smoothingFactor); + updateDropdownLabel(FFTSmoothingFactor.class, "fftSmoothingDropdown"); } public void setFilteringDropdownFrontend(FFTFilteredEnum _filteredEnum) { - String s = _filteredEnum.getString(); - cp5_widget.getController("fftFilteringDropdown").getCaptionLabel().setText(s); + widgetSettings.set(FFTFilteredEnum.class, _filteredEnum); + updateDropdownLabel(FFTFilteredEnum.class, "fftFilteringDropdown"); } }; @@ -233,10 +274,12 @@ public void fftSmoothingDropdown(int n) { globalFFTSettings.setSmoothingFactor(FFTSmoothingFactor.values()[n]); FFTSmoothingFactor smoothingFactor = globalFFTSettings.getSmoothingFactor(); ((W_BandPower) widgetManager.getWidget("W_BandPower")).setSmoothingDropdownFrontend(smoothingFactor); + ((W_Fft) widgetManager.getWidget("W_Fft")).setSmoothingDropdownFrontend(smoothingFactor); } public void fftFilteringDropdown(int n) { globalFFTSettings.setFilteredEnum(FFTFilteredEnum.values()[n]); FFTFilteredEnum filteredEnum = globalFFTSettings.getFilteredEnum(); ((W_BandPower) widgetManager.getWidget("W_BandPower")).setFilteringDropdownFrontend(filteredEnum); + ((W_Fft) widgetManager.getWidget("W_Fft")).setFilteringDropdownFrontend(filteredEnum); } \ No newline at end of file diff --git a/OpenBCI_GUI/W_TimeSeries.pde b/OpenBCI_GUI/W_TimeSeries.pde index e686a590e..26a1d4043 100644 --- a/OpenBCI_GUI/W_TimeSeries.pde +++ b/OpenBCI_GUI/W_TimeSeries.pde @@ -52,9 +52,6 @@ class W_TimeSeries extends WidgetWithSettings { tscp5 = new ControlP5(ourApplet); tscp5.setGraphics(ourApplet, 0, 0); tscp5.setAutoDraw(false); - cp5ElementsToCheck = new ArrayList(); - cp5ElementsToCheck.addAll(tsChanSelect.getCp5ElementsForOverlapCheck()); - xF = float(x); //float(int( ... is a shortcut for rounding the float down... so that it doesn't creep into the 1px margin yF = float(y); @@ -132,11 +129,18 @@ class W_TimeSeries extends WidgetWithSettings { // Initialize the channel select feature for this widget tsChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); - //Activate all channels in channelSelect by default for this widget + + // Activate all channels in channelSelect by default for this widget tsChanSelect.activateAllButtons(); - + + // Check and lock channel select if a dropdown that overlaps it is open + cp5ElementsToCheck = new ArrayList(); + cp5ElementsToCheck.addAll(tsChanSelect.getCp5ElementsForOverlapCheck()); + + // Save the active channels to the widget settings saveActiveChannels(tsChanSelect.getActiveChannels()); + // Save the current settings to the widget settings widgetSettings.saveDefaults(); } From 8b68e6b3b0e5d3ad3b0fe761447e2b5b1cd675c2 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 24 Apr 2025 15:58:18 -0500 Subject: [PATCH 20/29] Remove HeadPlot widget due to multiple bugs, glitches, and performance issues that cannot be easily fixed #1243 --- OpenBCI_GUI/DataProcessing.pde | 18 - OpenBCI_GUI/HeadPlotEnums.pde | 114 --- OpenBCI_GUI/W_HeadPlot.pde | 1259 -------------------------------- OpenBCI_GUI/WidgetManager.pde | 7 +- 4 files changed, 3 insertions(+), 1395 deletions(-) delete mode 100644 OpenBCI_GUI/HeadPlotEnums.pde delete mode 100644 OpenBCI_GUI/W_HeadPlot.pde diff --git a/OpenBCI_GUI/DataProcessing.pde b/OpenBCI_GUI/DataProcessing.pde index c5de43c14..3dcd513d5 100644 --- a/OpenBCI_GUI/DataProcessing.pde +++ b/OpenBCI_GUI/DataProcessing.pde @@ -298,24 +298,6 @@ class DataProcessing { headWidePower[i] = sum/globalChannelCount; // averaging power over all channels } - // Calculate data used for Headplot - // Find strongest channel - int refChanInd = findMax(data_std_uV); - //println("EEG_Processing: strongest chan (one referenced) = " + (refChanInd+1)); - float[] refData_uV = dataProcessingFilteredBuffer[refChanInd]; //use the filtered data - refData_uV = Arrays.copyOfRange(refData_uV, refData_uV.length-((int)fs_Hz), refData_uV.length); //just grab the most recent second of data - // Compute polarity of each channel - for (int channel=0; channel < globalChannelCount; channel++) { - float[] fooData_filt = dataProcessingFilteredBuffer[channel]; //use the filtered data - fooData_filt = Arrays.copyOfRange(fooData_filt, fooData_filt.length-((int)fs_Hz), fooData_filt.length); //just grab the most recent second of data - float dotProd = calcDotProduct(fooData_filt, refData_uV); - if (dotProd >= 0.0f) { - polarity[channel]=1.0; - } else { - polarity[channel]=-1.0; - } - } - ///////////////////////////////////////////////////////////// // Compute widget values independent of widgets being open // // -RW #1094 // diff --git a/OpenBCI_GUI/HeadPlotEnums.pde b/OpenBCI_GUI/HeadPlotEnums.pde deleted file mode 100644 index 34f669593..000000000 --- a/OpenBCI_GUI/HeadPlotEnums.pde +++ /dev/null @@ -1,114 +0,0 @@ - -public enum HeadPlotIntensity implements IndexingInterface { - INTENSITY_0_02 (0, .02f, "0.02x"), - INTENSITY_0_2 (1, .2f, "0.2x"), - INTENSITY_0_5 (2, .5f, "0.5x"), - INTENSITY_1 (3, 1.0f, "1x"), - INTENSITY_2 (4, 2.0f, "2x"), - INTENSITY_4 (5, 4.0f, "4x"); - - private int index; - private final float value; - private String label; - - HeadPlotIntensity(int index, float value, String label) { - this.index = index; - this.value = value; - this.label = label; - } - - public float getValue() { - return value; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } -} - -public enum HeadPlotPolarity implements IndexingInterface { - PLUS_AND_MINUS (0, "+/-"), - PLUS (1, "+"); - - private int index; - private String label; - - HeadPlotPolarity(int index, String label) { - this.index = index; - this.label = label; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } -} - -public enum HeadPlotContours implements IndexingInterface { - ON (0, "ON"), - OFF (1, "OFF"); - - private int index; - private String label; - - HeadPlotContours(int index, String label) { - this.index = index; - this.label = label; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } -} - -public enum HeadPlotSmoothing implements IndexingInterface { - NONE (0, 0.0f, "O.O"), - SMOOTH_50 (1, 0.5f, "0.5"), - SMOOTH_75 (2, 0.75f, "0.75"), - SMOOTH_90 (3, 0.9f, "0.9"), - SMOOTH_95 (4, 0.95f, "0.95"), - SMOOTH_98 (5, 0.98f, "0.98"), - SMOOTH_99 (6, 0.99f, "0.99"), - SMOOTH_999 (7, 0.999f, "0.999"); - - private int index; - private final float value; - private String label; - - HeadPlotSmoothing(int index, float value, String label) { - this.index = index; - this.value = value; - this.label = label; - } - - public float getValue() { - return value; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } -} \ No newline at end of file diff --git a/OpenBCI_GUI/W_HeadPlot.pde b/OpenBCI_GUI/W_HeadPlot.pde deleted file mode 100644 index d31f0db9e..000000000 --- a/OpenBCI_GUI/W_HeadPlot.pde +++ /dev/null @@ -1,1259 +0,0 @@ -//////////////////////////////////////////////////////////// -// // -// W_HeadPlot.pde // -// Created by: Conor Russomanno, November 2016 // -// Based on code written by: Chip Audette, Oct 2013 // -// Refactored by: Richard Waltman, April 2025 // -// // -//////////////////////////////////////////////////////////// - -import java.util.concurrent.locks.ReentrantLock; - -class W_HeadPlot extends Widget { - private HeadPlot headPlot; - private HeadPlotIntensity headPlotIntensity = HeadPlotIntensity.INTENSITY_1; - private HeadPlotPolarity headPlotPolarity = HeadPlotPolarity.PLUS_AND_MINUS; - private HeadPlotContours headPlotContours = HeadPlotContours.ON; - private HeadPlotSmoothing headPlotSmoothing = HeadPlotSmoothing.SMOOTH_98; - - private final float DEFAULT_VERTICAL_SCALE_UV = 200f; //this defines the Y-scale on the montage plots...this is the vertical space between traces - - W_HeadPlot() { - super(); - widgetTitle = "Head Plot"; - - List headPlotIntensityList = EnumHelper.getEnumStrings(HeadPlotIntensity.class); - List headPlotPolarityList = EnumHelper.getEnumStrings(HeadPlotPolarity.class); - List headPlotContoursList = EnumHelper.getEnumStrings(HeadPlotContours.class); - List headPlotSmoothingList = EnumHelper.getEnumStrings(HeadPlotSmoothing.class); - - addDropdown("headPlotIntensityDropdown", "Intensity", headPlotIntensityList, headPlotIntensity.getIndex()); - addDropdown("headPlotPolarityDropdown", "Polarity", headPlotPolarityList, headPlotPolarity.getIndex()); - addDropdown("headPlotContoursDropdown", "Contours", headPlotContoursList, headPlotContours.getIndex()); - addDropdown("headPlotSmoothingDropdown", "Smooth", headPlotSmoothingList, headPlotSmoothing.getIndex()); - - updateHeadPlot(); - } - - private void updateHeadPlot() { - headPlot = new HeadPlot(x, y, w, h, win_w, win_h); - headPlot.setIntensityData_byRef(dataProcessing.data_std_uV, is_railed); - headPlot.setPolarityData_byRef(dataProcessing.polarity); - } - - public void update(){ - super.update(); - headPlot.update(); - } - - public void draw(){ - super.draw(); - headPlot.draw(); //draw the actual headplot - } - - public void screenResized(){ - super.screenResized(); - headPlot.hp_x = x; - headPlot.hp_y = y; - headPlot.hp_w = w; - headPlot.hp_h = h; - headPlot.hp_win_x = x; - headPlot.hp_win_y = y; - - headPlot.setPositionSize(x, y, w, h, x, y); - } - - public void mousePressed(){ - super.mousePressed(); - headPlot.mousePressed(); - } - - public void mouseReleased(){ - super.mouseReleased(); - headPlot.mouseReleased(); - } - - public void mouseDragged(){ - super.mouseDragged(); - headPlot.mouseDragged(); - } - - public void setIntensity(int n) { - headPlotIntensity = headPlotIntensity.values()[n]; - float maxIntensityUv = DEFAULT_VERTICAL_SCALE_UV * headPlotIntensity.getValue(); - headPlot.setMaxIntensity_uV(maxIntensityUv); - } - - public void setPolarity(int n) { - headPlotPolarity = headPlotPolarity.values()[n]; - headPlot.use_polarity = headPlotPolarity == HeadPlotPolarity.PLUS_AND_MINUS; - } - - public void setContours(int n) { - headPlotContours = headPlotContours.values()[n]; - headPlot.drawHeadAsContours = headPlotContours == HeadPlotContours.ON; - } - - public void setSmoothing(int n) { - headPlotSmoothing = headPlotSmoothing.values()[n]; - headPlot.smoothingFactor = headPlotSmoothing.getValue(); - } -}; - -public void headPlotIntensityDropdown(int n) { - ((W_HeadPlot) widgetManager.getWidget("W_HeadPlot")).setIntensity(n); -} - -public void headPlotPolarityDropdown(int n) { - ((W_HeadPlot) widgetManager.getWidget("W_HeadPlot")).setPolarity(n); -} - -public void headPlotContoursDropdown(int n) { - ((W_HeadPlot) widgetManager.getWidget("W_HeadPlot")).setContours(n); -} - -public void headPlotSmoothingDropdown(int n) { - ((W_HeadPlot) widgetManager.getWidget("W_HeadPlot")).setSmoothing(n); -} - -//--------------------------------------------------------------------------------------------------------------------------------------- - -////////////////////////////////////////////////////////////// -// -// HeadPlot Class -// -// This class creates and manages the head-shaped plot used by the GUI. -// The head includes circles representing the different EEG electrodes. -// The color (brightness) of the electrodes can be adjusted so that the -// electrodes' brightness values dynamically reflect the intensity of the -// EEG signal. All EEG processing must happen outside of this class. -// -// Created by: Chip Audette 2013 -// -/////////////////////////////////////////////////////////////// - -// Note: This routine uses aliasing to know which data should be used to -// set the brightness of the electrodes. - -class HeadPlot { - private float rel_posX, rel_posY, rel_width, rel_height; - private int circ_x, circ_y, circ_diam; - private int earL_x, earL_y, earR_x, earR_y, ear_width, ear_height; - private int[] nose_x, nose_y; - private float[][] electrode_xy; - private float[] ref_electrode_xy; - private float[][][] electrode_color_weightFac; - private int[][] electrode_rgb; - private float[][] headVoltage; - private int elec_diam; - PFont font; - public float[] intensity_data_uV; - public float[] polarity_data; - private DataStatus[] is_railed; - private float intense_min_uV=0.0f, intense_max_uV=1.0f, assumed_railed_voltage_uV=1.0f; - private float log10_intense_min_uV = 0.0f, log10_intense_max_uV=1.0; - PImage headImage; - private int image_x, image_y; - public boolean drawHeadAsContours; - private boolean plot_color_as_log = true; - public float smoothingFactor = 0.0f; - private boolean use_polarity = true; - private int mouse_over_elec_index = -1; - private boolean isDragging = false; - private float drag_x, drag_y; - public int hp_win_x = 0; - public int hp_win_y = 0; - public int hp_x = 0; - public int hp_y = 0; - public int hp_w = 0; - public int hp_h = 0; - private final ReentrantLock lock = new ReentrantLock(); - private final AtomicBoolean hardCalculationsDone = new AtomicBoolean(false); - - HeadPlot(int _x, int _y, int _w, int _h, int _win_x, int _win_y) { - final int n_elec = globalChannelCount; //set number of electrodes using the global globalChannelCount variable - nose_x = new int[3]; - nose_y = new int[3]; - electrode_xy = new float[n_elec][2]; //x-y position of electrodes (pixels?) - ref_electrode_xy = new float[2]; //x-y position of reference electrode - electrode_rgb = new int[3][n_elec]; //rgb color for each electrode - font = p5; - drawHeadAsContours = true; //set this to be false for slower computers - - hp_x = _x; - hp_y = _y; - hp_w = _w; - hp_h = _h; - hp_win_x = _win_x; - hp_win_y = _win_y; - setMaxIntensity_uV(200.0f); //default intensity scaling for electrodes - } - - public void setPositionSize(int _x, int _y, int _w, int _h, int _win_x, int _win_y) { - float percentMargin = 0.1; - _x = _x + (int)(float(_w)*percentMargin); - _y = _y + (int)(float(_h)*percentMargin)-NAV_HEIGHT/2; - _w = (int)(float(_w)-(2*(float(_w)*percentMargin))); - _h = (int)(float(_h)-(2*(float(_h)*percentMargin))); - - rel_posX = float(_x)/_win_x; - rel_posY = float(_y)/_win_y; - rel_width = float(_w)/_win_x; - rel_height = float(_h)/_win_y; - setWindowDimensions(_win_x, _win_y); - } - - public void setIntensityData_byRef(float[] data, DataStatus[] is_rail) { - intensity_data_uV = data; //simply alias the data held externally. DOES NOT COPY THE DATA ITSELF! IT'S SIMPLY LINKED! - is_railed = is_rail; - } - - public void setPolarityData_byRef(float[] data) { - polarity_data = data;//simply alias the data held externally. DOES NOT COPY THE DATA ITSELF! IT'S SIMPLY LINKED! - } - - public String getUsePolarityTrueFalse() { - if (use_polarity) { - return "True"; - } else { - return "False"; - } - } - - public void setMaxIntensity_uV(float val_uV) { - intense_max_uV = val_uV; - intense_min_uV = intense_max_uV / 200.0 * 5.0f; //set to 200, get 5 - assumed_railed_voltage_uV = intense_max_uV; - - log10_intense_max_uV = log10(intense_max_uV); - log10_intense_min_uV = log10(intense_min_uV); - } - - public void set_plotColorAsLog(boolean state) { - plot_color_as_log = state; - } - - //this method defines all locations of all the subcomponents - public void setWindowDimensions(int win_width, int win_height) { - final int n_elec = electrode_xy.length; - - //define the head itself - float nose_relLen = 0.075f; - float nose_relWidth = 0.05f; - float nose_relGutter = 0.02f; - float ear_relLen = 0.15f; - float ear_relWidth = 0.075; - - float square_width = min(rel_width*(float)win_width, - rel_height*(float)win_height); //choose smaller of the two - - float total_width = square_width; - float total_height = square_width; - float nose_width = total_width * nose_relWidth; - float nose_height = total_height * nose_relLen; - ear_width = (int)(ear_relWidth * total_width); - ear_height = (int)(ear_relLen * total_height); - int circ_width_foo = (int)(total_width - 2.f*((float)ear_width)/2.0f); - int circ_height_foo = (int)(total_height - nose_height); - circ_diam = min(circ_width_foo, circ_height_foo); - - //locations: circle center, measured from upper left - circ_x = (int)((rel_posX+0.5f*rel_width)*(float)win_width); //center of head - circ_y = (int)((rel_posY+0.5*rel_height)*(float)win_height + nose_height); //center of head - - //locations: ear centers, measured from upper left - earL_x = circ_x - circ_diam/2; - earR_x = circ_x + circ_diam/2; - earL_y = circ_y; - earR_y = circ_y; - - //locations nose vertexes, measured from upper left - nose_x[0] = circ_x - (int)((nose_relWidth/2.f)*(float)win_width); - nose_x[1] = circ_x + (int)((nose_relWidth/2.f)*(float)win_width); - nose_x[2] = circ_x; - nose_y[0] = circ_y - (int)((float)circ_diam/2.0f - nose_relGutter*(float)win_height); - nose_y[1] = nose_y[0]; - nose_y[2] = circ_y - (int)((float)circ_diam/2.0f + nose_height); - - - //define the electrode positions as the relative position [-1.0 +1.0] within the head - //remember that negative "Y" is up and positive "Y" is down - float elec_relDiam = 0.12f; //was 0.1425 prior to 2014-03-23 - elec_diam = (int)(elec_relDiam*((float)circ_diam)); - setElectrodeLocations(n_elec, elec_relDiam); - - //define image to hold all of this - image_x = int(round(circ_x - 0.5*circ_diam - 0.5*ear_width)); - image_y = nose_y[2]; - headImage = createImage(int(total_width), int(total_height), ARGB); - headVoltage = new float[int(total_width)][int(total_height)]; // Initialize headVoltage here - - //initialize the image - for (int Iy=0; Iy < headImage.height; Iy++) { - for (int Ix = 0; Ix < headImage.width; Ix++) { - headImage.set(Ix, Iy, WHITE); - headVoltage[Ix][Iy] = 0.0f; // Initialize with default values - } - } - - //define the weighting factors to go from the electrode voltages - //outward to the full the contour plot - if (false) { - //here is a simple distance-based algorithm that works every time, though - //is not really physically accurate. It looks decent enough - computePixelWeightingFactors(); - } else { - //here is the better solution that is more physical. It involves an iterative - //solution, which could be really slow or could fail. If it does poorly, - //switch to using the algorithm above. - int n_wide_full = int(total_width); - int n_tall_full = int(total_height); - computePixelWeightingFactors_multiScale(n_wide_full, n_tall_full); - } - } //end of method - - - private void setElectrodeLocations(int n_elec, float elec_relDiam) { - //try loading the positions from a file - int n_elec_to_load = n_elec+1; //load the n_elec plus the reference electrode - Table elec_relXY = new Table(); - String default_fname = "electrode_positions_default.txt"; - //String default_fname = "electrode_positions_12elec_scalp9.txt"; - try { - elec_relXY = loadTable(default_fname, "header,csv"); //try loading the default file - } - catch (NullPointerException e) { - }; - - //get the default locations if the file didn't exist - if ((elec_relXY == null) || (elec_relXY.getRowCount() < n_elec_to_load)) { - println("headPlot: electrode position file not found or was wrong size: " + default_fname); - println(" : using defaults..."); - elec_relXY = createDefaultElectrodeLocations(default_fname, elec_relDiam); - } - - //define the actual locations of the electrodes in pixels - for (int i=0; i < min(electrode_xy.length, elec_relXY.getRowCount()); i++) { - electrode_xy[i][0] = circ_x+(int)(elec_relXY.getFloat(i, 0)*((float)circ_diam)); - electrode_xy[i][1] = circ_y+(int)(elec_relXY.getFloat(i, 1)*((float)circ_diam)); - } - - //the referenece electrode is last in the file - ref_electrode_xy[0] = circ_x+(int)(elec_relXY.getFloat(elec_relXY.getRowCount()-1, 0)*((float)circ_diam)); - ref_electrode_xy[1] = circ_y+(int)(elec_relXY.getFloat(elec_relXY.getRowCount()-1, 1)*((float)circ_diam)); - } - - private Table createDefaultElectrodeLocations(String fname, float elec_relDiam) { - - //regular electrodes - float[][] elec_relXY = new float[16][2]; - elec_relXY[0][0] = -0.125f; - elec_relXY[0][1] = -0.5f + elec_relDiam*(0.5f+0.2f); //FP1 - elec_relXY[1][0] = -elec_relXY[0][0]; - elec_relXY[1][1] = elec_relXY[0][1]; //FP2 - - elec_relXY[2][0] = -0.2f; - elec_relXY[2][1] = 0f; //C3 - elec_relXY[3][0] = -elec_relXY[2][0]; - elec_relXY[3][1] = elec_relXY[2][1]; //C4 - - elec_relXY[4][0] = -0.3425f; - elec_relXY[4][1] = 0.27f; //T5 (aka P7) - elec_relXY[5][0] = -elec_relXY[4][0]; - elec_relXY[5][1] = elec_relXY[4][1]; //T6 (aka P8) - - elec_relXY[6][0] = -0.125f; - elec_relXY[6][1] = +0.5f - elec_relDiam*(0.5f+0.2f); //O1 - elec_relXY[7][0] = -elec_relXY[6][0]; - elec_relXY[7][1] = elec_relXY[6][1]; //O2 - - elec_relXY[8][0] = elec_relXY[4][0]; - elec_relXY[8][1] = -elec_relXY[4][1]; //F7 - elec_relXY[9][0] = -elec_relXY[8][0]; - elec_relXY[9][1] = elec_relXY[8][1]; //F8 - - elec_relXY[10][0] = -0.18f; - elec_relXY[10][1] = -0.15f; //C3 - elec_relXY[11][0] = -elec_relXY[10][0]; - elec_relXY[11][1] = elec_relXY[10][1]; //C4 - - elec_relXY[12][0] = -0.5f +elec_relDiam*(0.5f+0.15f); - elec_relXY[12][1] = 0f; //T3 (aka T7?) - elec_relXY[13][0] = -elec_relXY[12][0]; - elec_relXY[13][1] = elec_relXY[12][1]; //T4 (aka T8) - - elec_relXY[14][0] = elec_relXY[10][0]; - elec_relXY[14][1] = -elec_relXY[10][1]; //CP3 - elec_relXY[15][0] = -elec_relXY[14][0]; - elec_relXY[15][1] = elec_relXY[14][1]; //CP4 - - //reference electrode - float[] ref_elec_relXY = new float[2]; - ref_elec_relXY[0] = 0.0f; - ref_elec_relXY[1] = 0.0f; - - //put it all into a table - Table table_elec_relXY = new Table(); - table_elec_relXY.addColumn("X", Table.FLOAT); - table_elec_relXY.addColumn("Y", Table.FLOAT); - for (int I = 0; I < elec_relXY.length; I++) { - table_elec_relXY.addRow(); - table_elec_relXY.setFloat(I, "X", elec_relXY[I][0]); - table_elec_relXY.setFloat(I, "Y", elec_relXY[I][1]); - } - - //last one is the reference electrode - table_elec_relXY.addRow(); - table_elec_relXY.setFloat(table_elec_relXY.getRowCount()-1, "X", ref_elec_relXY[0]); - table_elec_relXY.setFloat(table_elec_relXY.getRowCount()-1, "Y", ref_elec_relXY[1]); - - //try writing it to a file - String full_fname = "Data\\" + fname; - try { - saveTable(table_elec_relXY, full_fname, "csv"); - } - catch (NullPointerException e) { - println("headPlot: createDefaultElectrodeLocations: could not write file to " + full_fname); - }; - - //return - return table_elec_relXY; - } //end of method - - //Here, we do a two-step solution to get the weighting factors. - //We do a coarse grid first. We do our iterative solution on the coarse grid. - //Then, we formulate the full resolution fine grid. We interpolate these points - //from the data resulting from the coarse grid. - private void computePixelWeightingFactors_multiScale(int n_wide_full, int n_tall_full) { - int n_elec = electrode_xy.length; - - //define the coarse grid data structures and pixel locations - int decimation = 10; - int n_wide_small = n_wide_full / decimation + 1; - int n_tall_small = n_tall_full / decimation + 1; - float weightFac[][][] = new float[n_elec][n_wide_small][n_tall_small]; - int pixelAddress[][][] = new int[n_wide_small][n_tall_small][2]; - for (int Ix=0; Ix= 0.0. If it isn't, it was a - //quantization problem. let's clean it up. - for (int Ielec=0; Ielec -1) { - //we are! set the weightFac to reflect this electrode only - for (int Ielec=0; Ielec= 0) && (Iy_test < n_tall)) { - for (Ix_test=Ix-step; Ix_test<=Ix+step; Ix_test++) { - if ((Ix_test >=0) && (Ix_test < n_wide)) { - anyWithinBounds=true; - if (weightFac[Ix_test][Iy_test] >= 0.0) { - sum += weightFac[Ix_test][Iy_test]; - n_sum++; - } - } - } - } - - //along the right - Ix_test = Ix + step; - if ((Ix_test >= 0) && (Ix_test < n_wide)) { - for (Iy_test=Iy-step; Iy_test<=Iy+step; Iy_test++) { - if ((Iy_test >=0) && (Iy_test < n_tall)) { - anyWithinBounds=true; - if (weightFac[Ix_test][Iy_test] >= 0.0) { - sum += weightFac[Ix_test][Iy_test]; - n_sum++; - } - } - } - } - //along the bottom - Iy_test = Iy - step; - if ((Iy_test >= 0) && (Iy_test < n_tall)) { - for (Ix_test=Ix-step; Ix_test<=Ix+step; Ix_test++) { - if ((Ix_test >=0) && (Ix_test < n_wide)) { - anyWithinBounds=true; - if (weightFac[Ix_test][Iy_test] >= 0.0) { - sum += weightFac[Ix_test][Iy_test]; - n_sum++; - } - } - } - } - - //along the left - Ix_test = Ix - step; - if ((Ix_test >= 0) && (Ix_test < n_wide)) { - for (Iy_test=Iy-step; Iy_test<=Iy+step; Iy_test++) { - if ((Iy_test >=0) && (Iy_test < n_tall)) { - anyWithinBounds=true; - if (weightFac[Ix_test][Iy_test] >= 0.0) { - sum += weightFac[Ix_test][Iy_test]; - n_sum++; - } - } - } - } - - if (n_sum > 0) { - //some good pixels were found, so we have our answer - new_weightFac = sum / n_sum; //complete the averaging process - done = true; //we're done - } else { - //we did not find any good pixels. Step outward one more pixel and repeat the search - step++; //step outwward - if (anyWithinBounds) { //did the last iteration have some pixels that were at least within the domain - //some pixels were within the domain, so we have space to try again - done = false; - } else { - //no pixels were within the domain. We're out of space. We're done. - done = true; - } - } - } - return new_weightFac; //good or bad, return our new value - } - - private void computeWeightFactorsGivenOneElectrode_iterative(int toPixels[][][][], int toElectrodes[][][], int Ielec, float pixelVal[][][]) { - //Approach: pretend that one electrode is set to 1.0 and that all other electrodes are set to 0.0. - //Assume all of the pixels start at zero. Then, begin the simulation as if it were a transient - //solution where energy is coming in from the connections. Any excess energy will accumulate - //and cause the local pixel's value to increase. Iterate until the pixel values stabalize. - - int n_wide = toPixels.length; - int n_tall = toPixels[0].length; - int n_dir = toPixels[0][0].length; - float prevVal[][] = new float[n_wide][n_tall]; - float total, dVal; - int Ix_targ, Iy_targ; - float min_val=0.0f, max_val=0.0f; - boolean anyConnections = false; - int pixel_step = 1; - - //initialize all pixels to zero - //for (int Ix=0; Ix dVal_threshold)) { - //increment the counter - iter_count++; - - //reset our test value to a large value - max_dVal = 0.0f; - - //reset other values that I'm using for debugging - min_val = 1000.0f; //init to a big val - max_val = -1000.f; //init to a small val - - //copy current values - for (int Ix=0; Ix -1) { - Ix_targ = toPixels[Ix][Iy][Idir][0]; //x index of target pixel - Iy_targ = toPixels[Ix][Iy][Idir][1]; //y index of target pixel - total += (prevVal[Ix_targ][Iy_targ]-prevVal[Ix][Iy]); //difference relative to target pixel - anyConnections = true; - } - //do we connect to an electrode? - if (toElectrodes[Ix][Iy][Idir] > -1) { - //do we connect to the electrode that we're stimulating - if (toElectrodes[Ix][Iy][Idir] == Ielec) { - //yes, this is the active high one - total += (1.0-prevVal[Ix][Iy]); //difference relative to HIGH electrode - } else { - //no, this is a low one - total += (0.0-prevVal[Ix][Iy]); //difference relative to the LOW electrode - } - anyConnections = true; - } - } - - //compute the new pixel value - //if (numConnections[Ix][Iy] > 0) { - if (anyConnections) { - - //dVal = change_fac * (total - float(numConnections[Ix][Iy])*prevVal[Ix][Iy]); - dVal = change_fac * total; - pixelVal[Ielec][Ix][Iy] = prevVal[Ix][Iy] + dVal; - - //is this our worst change in value? - max_dVal = max(max_dVal, abs(dVal)); - - //update our other debugging values, too - min_val = min(min_val, pixelVal[Ielec][Ix][Iy]); - max_val = max(max_val, pixelVal[Ielec][Ix][Iy]); - } else { - pixelVal[Ielec][Ix][Iy] = -1.0; //means that there are no connections - } - } - } - //println("headPlot: computeWeightFactor: Ielec " + Ielec + ", iter = " + iter_count + ", max_dVal = " + max_dVal); - } - //println("headPlot: computeWeightFactor: Ielec " + Ielec + ", solution complete with " + iter_count + " iterations. min and max vals = " + min_val + ", " + max_val); - if (iter_count >= lim_iter_count) println("headPlot: computeWeightFactor: Ielec " + Ielec + ", solution complete with " + iter_count + " iterations. max_dVal = " + max_dVal); - } //end of method - - private void makeAllTheConnections(boolean withinHead[][], int withinElectrode[][], int toPixels[][][][], int toElectrodes[][][]) { - - int n_wide = toPixels.length; - int n_tall = toPixels[0].length; - int n_elec = electrode_xy.length; - int curPixel, Ipix, Ielec; - int n_pixels = n_wide * n_tall; - int Ix_try, Iy_try; - - //loop over every pixel in the image - for (int Iy=0; Iy < n_tall; Iy++) { - for (int Ix=0; Ix < n_wide; Ix++) { - - //loop over the four connections: left, right, up, down - for (int Idirection = 0; Idirection < 4; Idirection++) { - - Ix_try = -1; - Iy_try=-1; //nonsense values - switch (Idirection) { - case 0: - Ix_try = Ix-1; - Iy_try = Iy; //left - break; - case 1: - Ix_try = Ix+1; - Iy_try = Iy; //right - break; - case 2: - Ix_try = Ix; - Iy_try = Iy-1; //up - break; - case 3: - Ix_try = Ix; - Iy_try = Iy+1; //down - break; - } - - //initalize to no connection - toPixels[Ix][Iy][Idirection][0] = -1; - toPixels[Ix][Iy][Idirection][1] = -1; - toElectrodes[Ix][Iy][Idirection] = -1; - - //does the target pixel exist - if ((Ix_try >= 0) && (Ix_try < n_wide) && (Iy_try >= 0) && (Iy_try < n_tall)) { - //is the target pixel an electrode - if (withinElectrode[Ix_try][Iy_try] >= 0) { - //the target pixel is within an electrode - toElectrodes[Ix][Iy][Idirection] = withinElectrode[Ix_try][Iy_try]; - } else { - //the target pixel is not within an electrode. is it within the head? - if (withinHead[Ix_try][Iy_try]) { - toPixels[Ix][Iy][Idirection][0] = Ix_try; //save the address of the target pixel - toPixels[Ix][Iy][Idirection][1] = Iy_try; //save the address of the target pixel - } - } - } - } //end loop over direction of the target pixel - } //end loop over Ix - } //end loop over Iy - } // end of method - - private void whereAreThePixels(int pixelAddress[][][], boolean[][] withinHead, int[][] withinElectrode) { - int n_wide = pixelAddress.length; - int n_tall = pixelAddress[0].length; - int n_elec = electrode_xy.length; - int pixel_x, pixel_y; - int withinElecInd=-1; - float dist; - float elec_radius = 0.5*elec_diam; - - for (int Iy=0; Iy < n_tall; Iy++) { - //pixel_y = image_y + Iy; - for (int Ix = 0; Ix < n_wide; Ix++) { - //pixel_x = image_x + Ix; - - pixel_x = pixelAddress[Ix][Iy][0]+image_x; - pixel_y = pixelAddress[Ix][Iy][1]+image_y; - - //is it within the head - withinHead[Ix][Iy] = isPixelInsideHead(pixel_x, pixel_y); - - //compute distances of this pixel to each electrode - withinElecInd = -1; //reset for this pixel - for (int Ielec=0; Ielec < n_elec; Ielec++) { - //compute distance - dist = max(1.0, calcDistance(pixel_x, pixel_y, electrode_xy[Ielec][0], electrode_xy[Ielec][1])); - if (dist < elec_radius) withinElecInd = Ielec; - } - withinElectrode[Ix][Iy] = withinElecInd; //-1 means not inside an electrode - } //close Ix loop - } //close Iy loop - - //ensure that each electrode is at at least one pixel - for (int Ielec=0; Ielec= 0.0) { //zero and positive values are inside the head - //it is inside the head. set the color based on the electrodes - headImage.set(Ix, Iy, calcPixelColor(Ix, Iy)); - } else { //negative values are outside of the head - headImage.set(Ix, Iy, WHITE); - } - } - } - } - - private void convertVoltagesToHeadImage() { - if (headImage == null || electrode_color_weightFac == null || headVoltage == null) { - println("ERROR: HeadPlot data structures not initialized"); - return; - } - for (int Iy=0; Iy < headImage.height; Iy++) { - for (int Ix = 0; Ix < headImage.width; Ix++) { - //is this pixel inside the head? - if (electrode_color_weightFac[0][Ix][Iy] >= 0.0) { //zero and positive values are inside the head - //it is inside the head. set the color based on the electrodes - headVoltage[Ix][Iy] = calcPixelVoltage(Ix, Iy, headVoltage[Ix][Iy]); - headImage.set(Ix, Iy, calcPixelColor(headVoltage[Ix][Iy])); - } else { //negative values are outside of the head - //pixel is outside the head. set to black. - headVoltage[Ix][Iy] = -1.0; - headImage.set(Ix, Iy, WHITE); - } - } - } - } - - private float calcPixelVoltage(int pixel_Ix, int pixel_Iy, float prev_val) { - float weight, elec_volt; - int n_elec = electrode_xy.length; - float voltage = 0.0f; - float low = intense_min_uV; - float high = intense_max_uV; - - for (int Ielec=0; Ielec 0.0f) voltage = smoothingFactor*prev_val + (1.0-smoothingFactor)*voltage; - - return voltage; - } - - - private color calcPixelColor(float pixel_volt_uV) { - // float new_rgb[] = {255.0, 0.0, 0.0}; //init to red - //224, 56, 45 - float new_rgb[] = {224.0, 56.0, 45.0}; //init to red - // float new_rgb[] = {0.0, 255.0, 0.0}; //init to red - //54, 87, 158 - if (pixel_volt_uV < 0.0) { - //init to blue instead - new_rgb[0]=54.0; - new_rgb[1]=87.0; - new_rgb[2]=158.0; - // new_rgb[0]=0.0; - // new_rgb[1]=0.0; - // new_rgb[2]=255.0; - } - float val; - - - float intensity = constrain(abs(pixel_volt_uV), intense_min_uV, intense_max_uV); - if (plot_color_as_log) { - intensity = map(log10(intensity), - log10_intense_min_uV, - log10_intense_max_uV, - 0.0f, 1.0f); - } else { - intensity = map(intensity, - intense_min_uV, - intense_max_uV, - 0.0f, 1.0f); - } - - //make the intensity fade NOT from black->color, but from white->color - for (int i=0; i < 3; i++) { - val = ((float)new_rgb[i]) / 255.f; - new_rgb[i] = ((val + (1.0f - val)*(1.0f-intensity))*255.f); //adds in white at low intensity. no white at high intensity - new_rgb[i] = constrain(new_rgb[i], 0.0, 255.0); - } - - //quantize the color to make contour-style plot? - if (true) quantizeColor(new_rgb); - - return color(int(new_rgb[0]), int(new_rgb[1]), int(new_rgb[2]), 255); - } - - private void quantizeColor(float new_rgb[]) { - int n_colors = 12; - int ticks_per_color = 256 / (n_colors+1); - for (int Irgb=0; Irgb<3; Irgb++) new_rgb[Irgb] = min(255.0, float(int(new_rgb[Irgb]/ticks_per_color))*ticks_per_color); - } - - - //compute the color of the pixel given the location - private color calcPixelColor(int pixel_Ix, int pixel_Iy) { - float weight; - - //compute the weighted average using the precomputed factors - float new_rgb[] = {0.0, 0.0, 0.0}; //init to zeros - for (int Ielec=0; Ielec < electrode_xy.length; Ielec++) { - //int Ielec = 0; - weight = electrode_color_weightFac[Ielec][pixel_Ix][pixel_Iy]; - for (int Irgb=0; Irgb<3; Irgb++) { - new_rgb[Irgb] += weight*electrode_rgb[Irgb][Ielec]; - } - } - - //quantize the color to make contour-style plot? - if (true) quantizeColor(new_rgb); - - return color(int(new_rgb[0]), int(new_rgb[1]), int(new_rgb[2]), 255); - } - - private float calcDistance(int x, int y, float ref_x, float ref_y) { - float dx = float(x) - ref_x; - float dy = float(y) - ref_y; - return sqrt(dx*dx + dy*dy); - } - - //compute color for the electrode value - private void updateElectrodeColors() { - int rgb[] = new int[]{255, 0, 0}; //color for the electrode when fully light - float intensity; - float val; - int new_rgb[] = new int[3]; - float low = intense_min_uV; - float high = intense_max_uV; - float log_low = log10_intense_min_uV; - float log_high = log10_intense_max_uV; - for (int Ielec=0; Ielec < electrode_xy.length; Ielec++) { - intensity = constrain(intensity_data_uV[Ielec], low, high); - if (plot_color_as_log) { - intensity = map(log10(intensity), log_low, log_high, 0.0f, 1.0f); - } else { - intensity = map(intensity, low, high, 0.0f, 1.0f); - } - - //make the intensity fade NOT from black->color, but from white->color - for (int i=0; i < 3; i++) { - val = ((float)rgb[i]) / 255.f; - new_rgb[i] = (int)((val + (1.0f - val)*(1.0f-intensity))*255.f); //adds in white at low intensity. no white at high intensity - new_rgb[i] = constrain(new_rgb[i], 0, 255); - } - - //change color to dark RED if railed - if (is_railed[Ielec].is_railed) new_rgb = new int[]{127, 0, 0}; - - //set the electrode color - electrode_rgb[0][Ielec] = new_rgb[0]; - electrode_rgb[1][Ielec] = new_rgb[1]; - electrode_rgb[2][Ielec] = new_rgb[2]; - } - } - - private boolean isMouseOverElectrode(int n){ - float elec_mouse_x_dist = electrode_xy[n][0] - mouseX; - float elec_mouse_y_dist = electrode_xy[n][1] - mouseY; - return elec_mouse_x_dist * elec_mouse_x_dist + elec_mouse_y_dist * elec_mouse_y_dist < elec_diam * elec_diam / 4; - } - - private boolean isDraggedElecInsideHead() { - int dx = mouseX - circ_x; - int dy = mouseY - circ_y; - return dx * dx + dy * dy < (circ_diam - elec_diam) * (circ_diam - elec_diam) / 4; - } - - void mousePressed() { - if (mouse_over_elec_index > -1) { - isDragging = true; - drag_x = mouseX - electrode_xy[mouse_over_elec_index][0]; - drag_y = mouseY - electrode_xy[mouse_over_elec_index][1]; - } else { - isDragging = false; - } - } - - void mouseDragged() { - if (isDragging && mouse_over_elec_index > -1 && isDraggedElecInsideHead()) { - electrode_xy[mouse_over_elec_index][0] = mouseX - drag_x; - electrode_xy[mouse_over_elec_index][1] = mouseY - drag_y; - } - } - - void mouseReleased() { - isDragging = false; - } - - public boolean isPixelInsideHead(int pixel_x, int pixel_y) { - int dx = pixel_x - circ_x; - int dy = pixel_y - circ_y; - float r = sqrt(float(dx*dx) + float(dy*dy)); - if (r <= 0.5*circ_diam) { - return true; - } else { - return false; - } - } - - public void update() { - //do this when new data is available - if (!hardCalculationsDone.get()) { - setPositionSize(hp_x, hp_y, hp_w, hp_h, hp_win_x, hp_win_y); - hardCalculationsDone.set(true); - } - - //update electrode colors - updateElectrodeColors(); - - //update head voltages - if (hardCalculationsDone.get() && headVoltage != null) { - convertVoltagesToHeadImage(); - } - } - - public void draw() { - - if (!hardCalculationsDone.get() || headImage == null) { - return; - } - - pushStyle(); - smooth(); - //draw head parts - fill(WHITE); - stroke(GREY_125); - triangle(nose_x[0], nose_y[0], nose_x[1], nose_y[1], nose_x[2], nose_y[2]); //nose - ellipse(earL_x, earL_y, ear_width, ear_height); //little circle for the ear - ellipse(earR_x, earR_y, ear_width, ear_height); //little circle for the ear - - //draw head itself - fill(WHITE); //fill in a white head - strokeWeight(1); - ellipse(circ_x, circ_y, circ_diam, circ_diam); //big circle for the head - if (drawHeadAsContours) { - //add the contnours - image(headImage, image_x, image_y); - noFill(); //overlay a circle as an outline, but no fill - strokeWeight(1); - ellipse(circ_x, circ_y, circ_diam, circ_diam); //big circle for the head - } - - //draw electrodes on the head - if (!isDragging) { - mouse_over_elec_index = -1; - } - for (int Ielec=0; Ielec < electrode_xy.length; Ielec++) { - if (drawHeadAsContours) { - noFill(); //make transparent to allow color to come through from below - } else { - fill(electrode_rgb[0][Ielec], electrode_rgb[1][Ielec], electrode_rgb[2][Ielec]); - } - if (!isDragging && isMouseOverElectrode(Ielec)) { - //electrode with a bigger index gets priority in dragging - mouse_over_elec_index = Ielec; - strokeWeight(2); - } else if (mouse_over_elec_index == Ielec) { - strokeWeight(2); - } else{ - strokeWeight(1); - } - ellipse(electrode_xy[Ielec][0], electrode_xy[Ielec][1], elec_diam, elec_diam); //electrode circle - } - - //add labels to electrodes - fill(OPENBCI_DARKBLUE); - textFont(font); - textAlign(CENTER, CENTER); - for (int i=0; i < electrode_xy.length; i++) { - //text(Integer.toString(i),electrode_xy[i][0], electrode_xy[i][1]); - text(i+1, electrode_xy[i][0], electrode_xy[i][1]); - } - text("R", ref_electrode_xy[0], ref_electrode_xy[1]); - - popStyle(); - } //end of draw method - - private void doHardCalculations() { - if (hardCalculationsDone.get()) return; - - lock.lock(); - try { - if (hardCalculationsDone.compareAndSet(false, true)) { - setPositionSize(hp_x, hp_y, hp_w, hp_h, hp_win_x, hp_win_y); - } - } finally { - lock.unlock(); - } - } -}; diff --git a/OpenBCI_GUI/WidgetManager.pde b/OpenBCI_GUI/WidgetManager.pde index 860a86d5f..9363c9b81 100644 --- a/OpenBCI_GUI/WidgetManager.pde +++ b/OpenBCI_GUI/WidgetManager.pde @@ -3,8 +3,9 @@ //======================================================================================== /* Notes: - - The order in which they are added will effect the order in which they appear in the GUI and in the WidgetSelector dropdown menu of each widget - - Use the WidgetTemplate.pde file as a starting point for creating new widgets (also check out W_TimeSeries.pde, W_Fft.pde, and W_HeadPlot.pde) + - The order in which they are added will effect the order in which they appear in the GUI and in the WidgetSelector dropdown menu of each widget. + - Use the WidgetTemplate.pde file as a starting point for creating new widgets. + - Also, check out W_TimeSeries.pde, W_Fft.pde, and W_Accelerometer.pde for examples. */ //======================================================================================== //======================================================================================== @@ -64,8 +65,6 @@ class WidgetManager { widgets.add(new W_BandPower()); - widgets.add(new W_HeadPlot()); - widgets.add(new W_Emg()); widgets.add(new W_EmgJoystick()); From a29d9bccbbaa253c7f107bf644885055855d37fa Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 24 Apr 2025 16:41:03 -0500 Subject: [PATCH 21/29] Add WidgetWithSettings to EMG and EMGJoystick widgets --- OpenBCI_GUI/W_EMG.pde | 34 ++++++++++++++++++++++++---------- OpenBCI_GUI/W_EMGJoystick.pde | 23 +++++++++++++++-------- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/OpenBCI_GUI/W_EMG.pde b/OpenBCI_GUI/W_EMG.pde index 301c74c75..f53a6bc82 100644 --- a/OpenBCI_GUI/W_EMG.pde +++ b/OpenBCI_GUI/W_EMG.pde @@ -13,26 +13,17 @@ // TODO: Add dynamic threshold functionality //////////////////////////////////////////////////////////////////////////////// -class W_Emg extends Widget { +class W_Emg extends WidgetWithSettings { private ControlP5 emgCp5; private Button emgSettingsButton; private final int EMG_SETTINGS_BUTTON_WIDTH = 125; private List cp5ElementsToCheck; - public ExGChannelSelect emgChannelSelect; W_Emg () { super(); widgetTitle = "EMG"; - cp5ElementsToCheck = new ArrayList(); - - //Add channel select dropdown to this widget - emgChannelSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); - emgChannelSelect.activateAllButtons(); - - cp5ElementsToCheck.addAll(emgChannelSelect.getCp5ElementsForOverlapCheck()); - emgCp5 = new ControlP5(ourApplet); emgCp5.setGraphics(ourApplet, 0,0); emgCp5.setAutoDraw(false); @@ -41,6 +32,29 @@ class W_Emg extends Widget { cp5ElementsToCheck.add((controlP5.Controller) emgSettingsButton); } + @Override + protected void initWidgetSettings() { + super.initWidgetSettings(); + emgChannelSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); + emgChannelSelect.activateAllButtons(); + cp5ElementsToCheck = new ArrayList(); + cp5ElementsToCheck.addAll(emgChannelSelect.getCp5ElementsForOverlapCheck()); + saveActiveChannels(emgChannelSelect.getActiveChannels()); + widgetSettings.saveDefaults(); + } + + @Override + protected void applySettings() { + applyActiveChannels(emgChannelSelect); + } + + @Override + protected void updateChannelSettings() { + if (emgChannelSelect != null) { + saveActiveChannels(emgChannelSelect.getActiveChannels()); + } + } + public void update() { super.update(); lockElementsOnOverlapCheck(cp5ElementsToCheck); diff --git a/OpenBCI_GUI/W_EMGJoystick.pde b/OpenBCI_GUI/W_EMGJoystick.pde index 1641284c5..203d1e8ba 100644 --- a/OpenBCI_GUI/W_EMGJoystick.pde +++ b/OpenBCI_GUI/W_EMGJoystick.pde @@ -9,7 +9,7 @@ // // ///////////////////////////////////////////////////////////////////////////////////////////////////////// -class W_EmgJoystick extends Widget { +class W_EmgJoystick extends WidgetWithSettings { private ControlP5 emgCp5; private Button emgSettingsButton; private List cp5ElementsToCheck; @@ -46,8 +46,6 @@ class W_EmgJoystick extends Widget { private String[] plotChannelLabels = new String[NUM_EMG_INPUTS]; - public EmgJoystickSmoothing joystickSmoothing = EmgJoystickSmoothing.POINT_9; - private int DROPDOWN_HEIGHT = navH - 4; private int DROPDOWN_WIDTH = 80; private int DROPDOWN_SPACER = 10; @@ -96,11 +94,20 @@ class W_EmgJoystick extends Widget { plotChannelLabels[i] = Integer.toString(emgJoystickInputs.getInput(i).getIndex() + 1); } - List joystickSmoothingList = EnumHelper.getEnumStrings(EmgJoystickSmoothing.class); + createInputDropdowns(); + } - addDropdown("emgJoystickSmoothingDropdown", "Smoothing", joystickSmoothingList, joystickSmoothing.getIndex()); + @Override + protected void initWidgetSettings() { + super.initWidgetSettings(); + widgetSettings.set(EmgJoystickSmoothing.class, EmgJoystickSmoothing.POINT_9); + initDropdown(EmgJoystickSmoothing.class, "emgJoystickSmoothingDropdown", "Smoothing"); + widgetSettings.saveDefaults(); + } - createInputDropdowns(); + @Override + protected void applySettings() { + updateDropdownLabel(EmgJoystickSmoothing.class, "emgJoystickSmoothingDropdown"); } public void update(){ @@ -234,7 +241,7 @@ class W_EmgJoystick extends Widget { joystickRawX = unitCircleXY[0]; joystickRawY = unitCircleXY[1]; //Lerp the joystick values to smooth them out - float amount = 1.0f - joystickSmoothing.getValue(); + float amount = 1.0f - widgetSettings.get(EmgJoystickSmoothing.class).getValue(); joystickRawX = lerp(previousJoystickRawX, joystickRawX, amount); joystickRawY = lerp(previousJoystickRawY, joystickRawY, amount); } @@ -316,7 +323,7 @@ class W_EmgJoystick extends Widget { } public void setJoystickSmoothing(int n) { - joystickSmoothing = joystickSmoothing.values()[n]; + widgetSettings.setByIndex(EmgJoystickSmoothing.class, n); } private void createEmgSettingsButton() { From 2a465c114f6f4e2b0ac52b8e19997ea7b03420ad Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 24 Apr 2025 17:12:12 -0500 Subject: [PATCH 22/29] Add WidgetWithSettings to Spectrogram Widget and account for dual channel selects --- OpenBCI_GUI/SpectrogramEnums.pde | 2 +- OpenBCI_GUI/W_Spectrogram.pde | 170 ++++++++++++++----------------- OpenBCI_GUI/Widget.pde | 43 ++++++++ OpenBCI_GUI/WidgetSettings.pde | 35 +++++++ 4 files changed, 154 insertions(+), 96 deletions(-) diff --git a/OpenBCI_GUI/SpectrogramEnums.pde b/OpenBCI_GUI/SpectrogramEnums.pde index d67cc62b2..7b1a6d86e 100644 --- a/OpenBCI_GUI/SpectrogramEnums.pde +++ b/OpenBCI_GUI/SpectrogramEnums.pde @@ -38,7 +38,7 @@ public enum SpectrogramMaxFrequency implements IndexingInterface { } public enum SpectrogramWindowSize implements IndexingInterface { - ONE_MINUTE (0, 1f, "1 Min.", new float[]{1, .5, 0}, 1000), + ONE_MINUTE (0, 1f, "1 Min.", new float[]{1, .5, 0}, 25), ONE_MINUTE_THIRTY (1, 1.5f, "1.5 Min.", new float[]{1.5, 1, .5, 0}, 50), THREE_MINUTES (2, 3f, "3 Min.", new float[]{3, 2, 1, 0}, 100), SIX_MINUTES (3, 6f, "6 Min.", new float[]{6, 5, 4, 3, 2, 1, 0}, 200), diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index f0d5e5c5a..a4f833021 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -3,16 +3,16 @@ // W_Spectrogram.pde // // // // // -// Created by: Richard Waltman, September 2019 // +// Created by: Richard Waltman, September 2019 // +// Refactored by: Richard Waltman, April 2025 // // // ////////////////////////////////////////////////////// -class W_Spectrogram extends Widget { - //to see all core variables/methods of the Widget class, refer to Widget.pde +class W_Spectrogram extends WidgetWithSettings { public ExGChannelSelect spectChanSelectTop; public ExGChannelSelect spectChanSelectBot; private boolean chanSelectWasOpen = false; - List cp5ElementsToCheck = new ArrayList(); + List cp5ElementsToCheck; int xPos = 0; int hueLimit = 160; @@ -43,22 +43,10 @@ class W_Spectrogram extends Widget { float[] topFFTAvg; float[] botFFTAvg; - private SpectrogramMaxFrequency maxFrequency = SpectrogramMaxFrequency.MAX_60; - private SpectrogramWindowSize windowSize = SpectrogramWindowSize.ONE_MINUTE; - private FFTLogLin logLin = FFTLogLin.LIN; - W_Spectrogram() { super(); widgetTitle = "Spectrogram"; - //Add channel select dropdown to this widget - spectChanSelectTop = new DualExGChannelSelect(ourApplet, x, y, w, navH, true); - spectChanSelectBot = new DualExGChannelSelect(ourApplet, x, y + navH, w, navH, false); - activateDefaultChannels(); - - cp5ElementsToCheck.addAll(spectChanSelectTop.getCp5ElementsForOverlapCheck()); - cp5ElementsToCheck.addAll(spectChanSelectBot.getCp5ElementsForOverlapCheck()); - xPos = w - 1; //draw on the right, and shift pixels to the left prevW = w; prevH = h; @@ -70,20 +58,62 @@ class W_Spectrogram extends Widget { //Fetch/calculate the time strings for the horizontal axis ticks horizontalAxisLabelStrings = fetchTimeStrings(); - List maxFrequencyList = EnumHelper.getEnumStrings(SpectrogramMaxFrequency.class); - List windowSizeList = EnumHelper.getEnumStrings(SpectrogramWindowSize.class); - List logLinList = EnumHelper.getEnumStrings(FFTLogLin.class); - - addDropdown("spectrogramMaxFrequencyDropdown", "Max Hz", maxFrequencyList, maxFrequency.getIndex()); - addDropdown("spectrogramWindowDropdown", "Window", windowSizeList, windowSize.getIndex()); - addDropdown("spectrogramLogLinDropdown", "Log/Lin", logLinList, logLin.getIndex()); - //Resize the height of the data image using default + SpectrogramMaxFrequency maxFrequency = widgetSettings.get(SpectrogramMaxFrequency.class); dataImageH = maxFrequency.getAxisLabels()[0] * 2; //Create image using correct dimensions! Fixes bug where image size and labels do not align on session start. dataImg = createImage(dataImageW, dataImageH, RGB); } + @Override + protected void initWidgetSettings() { + super.initWidgetSettings(); + widgetSettings.set(SpectrogramMaxFrequency.class, SpectrogramMaxFrequency.MAX_60) + .set(SpectrogramWindowSize.class, SpectrogramWindowSize.ONE_MINUTE) + .set(FFTLogLin.class, FFTLogLin.LIN); + + initDropdown(SpectrogramMaxFrequency.class, "spectrogramMaxFrequencyDropdown", "Max Hz"); + initDropdown(SpectrogramWindowSize.class, "spectrogramWindowDropdown", "Window"); + initDropdown(FFTLogLin.class, "spectrogramLogLinDropdown", "Log/Lin"); + + spectChanSelectTop = new DualExGChannelSelect(ourApplet, x, y, w, navH, true); + spectChanSelectBot = new DualExGChannelSelect(ourApplet, x, y + navH, w, navH, false); + activateDefaultChannels(); + + // Save both channel selections with unique identifiers + saveNamedChannels("top", spectChanSelectTop.getActiveChannels()); + saveNamedChannels("bottom", spectChanSelectBot.getActiveChannels()); + + cp5ElementsToCheck = new ArrayList(); + cp5ElementsToCheck.addAll(spectChanSelectTop.getCp5ElementsForOverlapCheck()); + cp5ElementsToCheck.addAll(spectChanSelectBot.getCp5ElementsForOverlapCheck()); + } + + @Override + protected void applySettings() { + updateDropdownLabel(SpectrogramMaxFrequency.class, "spectrogramMaxFrequencyDropdown"); + updateDropdownLabel(SpectrogramWindowSize.class, "spectrogramWindowDropdown"); + updateDropdownLabel(FFTLogLin.class, "spectrogramLogLinDropdown"); + applyMaxFrequency(); + applyWindowSize(); + + // Apply saved channel selections if available + if (hasNamedChannels("top")) { + applyNamedChannels("top", spectChanSelectTop); + } + + if (hasNamedChannels("bottom")) { + applyNamedChannels("bottom", spectChanSelectBot); + } + } + + @Override + protected void updateChannelSettings() { + // Save current channel selections before saving settings + saveNamedChannels("top", spectChanSelectTop.getActiveChannels()); + saveNamedChannels("bottom", spectChanSelectBot.getActiveChannels()); + } + void update(){ super.update(); @@ -148,8 +178,11 @@ class W_Spectrogram extends Widget { //draw the spectrogram if the widget is open, and update pixels if board is streaming data if (currentBoard.isStreaming()) { pushStyle(); + dataImg.loadPixels(); + FFTLogLin logLin = widgetSettings.get(FFTLogLin.class); + //Shift all pixels to the left! (every scrollspeed ms) if(millis() - lastShift > scrollSpeed) { for (int r = 0; r < dataImg.height; r++) { @@ -265,6 +298,7 @@ class W_Spectrogram extends Widget { fill(255); strokeWeight(2); textSize(11); + SpectrogramWindowSize windowSize = widgetSettings.get(SpectrogramWindowSize.class); int horizontalAxisDivCount = windowSize.getAxisLabels().length; for (int i = 0; i < horizontalAxisDivCount; i++) { float offset = scaledW * dataImageW * (float(i) / horizontalAxisDivCount); @@ -297,6 +331,7 @@ class W_Spectrogram extends Widget { fill(255); textSize(12); strokeWeight(2); + SpectrogramMaxFrequency maxFrequency = widgetSettings.get(SpectrogramMaxFrequency.class); int verticalAxisDivCount = maxFrequency.getAxisLabels().length - 1; for (int i = 0; i < verticalAxisDivCount; i++) { float offset = scaledH * dataImageH * (float(i) / verticalAxisDivCount); @@ -329,6 +364,7 @@ class W_Spectrogram extends Widget { return; } } + FFTLogLin logLin = widgetSettings.get(FFTLogLin.class); pushStyle(); //draw color scale reference to the right of the spectrogram for (int i = 0; i < colorScaleHeight; i++) { @@ -405,6 +441,7 @@ class W_Spectrogram extends Widget { time = LocalDateTime.ofInstant(Instant.ofEpochMilli(getCurrentTimeStamp()), TimeZone.getDefault().toZoneId()); } + SpectrogramWindowSize windowSize = widgetSettings.get(SpectrogramWindowSize.class); for (int i = 0; i < windowSize.getAxisLabels().length; i++) { long l = (long)(windowSize.getAxisLabels()[i] * 60f); LocalDateTime t = time.minus(l, ChronoUnit.SECONDS); @@ -434,24 +471,33 @@ class W_Spectrogram extends Widget { } public void setLogLin(int n) { - logLin = logLin.values()[n]; + widgetSettings.setByIndex(FFTLogLin.class, n); } public void setMaxFrequency(int n) { - maxFrequency = maxFrequency.values()[n]; + widgetSettings.setByIndex(SpectrogramMaxFrequency.class, n); + applyMaxFrequency(); + } + + public void setWindowSize(int n) { + widgetSettings.setByIndex(SpectrogramWindowSize.class, n); + applyWindowSize(); + } + + private void applyMaxFrequency() { + SpectrogramMaxFrequency maxFrequency = widgetSettings.get(SpectrogramMaxFrequency.class); // Resize the height of the data image dataImageH = maxFrequency.getAxisLabels()[0] * 2; // Overwrite the existing image dataImg = createImage(dataImageW, dataImageH, RGB); } - public void setWindowSize(int n) { - windowSize = windowSize.values()[n]; + private void applyWindowSize() { + SpectrogramWindowSize windowSize = widgetSettings.get(SpectrogramWindowSize.class); setScrollSpeed(windowSize.getScrollSpeed()); horizontalAxisLabelStrings.clear(); horizontalAxisLabelStrings = fetchTimeStrings(); dataImg = createImage(dataImageW, dataImageH, RGB); - } }; @@ -466,69 +512,3 @@ public void spectrogramWindowDropdown(int n) { public void spectrogramLogLinDropdown(int n) { ((W_Spectrogram) widgetManager.getWidget("W_Spectrogram")).setLogLin(n); } - - -//Save data from the Active channel checkBoxes - Top -//JSONArray saveActiveChanSpectTop = new JSONArray(); -/* -//FIX ME -int numActiveSpectChanTop = w_spectrogram.spectChanSelectTop.getActiveChannels().size(); -for (int i = 0; i < numActiveSpectChanTop; i++) { - int activeChannel = w_spectrogram.spectChanSelectTop.getActiveChannels().get(i); - saveActiveChanSpectTop.setInt(i, activeChannel); -} -saveSpectrogramSettings.setJSONArray("activeChannelsTop", saveActiveChanSpectTop); -//Save data from the Active channel checkBoxes - Bottom -JSONArray saveActiveChanSpectBot = new JSONArray(); -int numActiveSpectChanBot = w_spectrogram.spectChanSelectBot.getActiveChannels().size(); -for (int i = 0; i < numActiveSpectChanBot; i++) { - int activeChannel = w_spectrogram.spectChanSelectBot.getActiveChannels().get(i); - saveActiveChanSpectBot.setInt(i, activeChannel); -} -*/ -//saveSpectrogramSettings.setJSONArray("activeChannelsBot", saveActiveChanSpectBot); -//Save Spectrogram_Max Freq Setting. The max frq variable is updated every time the user selects a dropdown in the spectrogram widget -//FIX ME -/* -saveSpectrogramSettings.setInt("Spectrogram_Max Freq", spectMaxFrqSave); -saveSpectrogramSettings.setInt("Spectrogram_Sample Rate", spectSampleRateSave); -saveSpectrogramSettings.setInt("Spectrogram_LogLin", spectLogLinSave); -*/ - -//Get Band Power widget settings - //FIX ME - /* - loadBPActiveChans.clear(); - JSONObject loadBPSettings = loadSettingsJSONData.getJSONObject(kJSONKeyBandPower); - JSONArray loadBPChan = loadBPSettings.getJSONArray("activeChannels"); - for (int i = 0; i < loadBPChan.size(); i++) { - loadBPActiveChans.add(loadBPChan.getInt(i)); - } - loadBPAutoClean = loadBPSettings.getInt("bpAutoClean"); - loadBPAutoCleanThreshold = loadBPSettings.getInt("bpAutoCleanThreshold"); - loadBPAutoCleanTimer = loadBPSettings.getInt("bpAutoCleanTimer"); - //println("Settings: band power active chans loaded = " + loadBPActiveChans ); - */ - - /* - try { - //Get Spectrogram widget settings - loadSpectActiveChanTop.clear(); - loadSpectActiveChanBot.clear(); - JSONObject loadSpectSettings = loadSettingsJSONData.getJSONObject(kJSONKeySpectrogram); - JSONArray loadSpectChanTop = loadSpectSettings.getJSONArray("activeChannelsTop"); - for (int i = 0; i < loadSpectChanTop.size(); i++) { - loadSpectActiveChanTop.add(loadSpectChanTop.getInt(i)); - } - JSONArray loadSpectChanBot = loadSpectSettings.getJSONArray("activeChannelsBot"); - for (int i = 0; i < loadSpectChanBot.size(); i++) { - loadSpectActiveChanBot.add(loadSpectChanBot.getInt(i)); - } - spectMaxFrqLoad = loadSpectSettings.getInt("Spectrogram_Max Freq"); - spectSampleRateLoad = loadSpectSettings.getInt("Spectrogram_Sample Rate"); - spectLogLinLoad = loadSpectSettings.getInt("Spectrogram_LogLin"); - //println(loadSpectActiveChanTop, loadSpectActiveChanBot); - } catch (Exception e) { - e.printStackTrace(); - } -*/ \ No newline at end of file diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index 4ccc158e1..c2a3ef4e5 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -437,6 +437,49 @@ abstract class WidgetWithSettings extends Widget { // Default implementation does nothing // Override in widgets that have channel selectors } + + /** + * Save active channel selection to widget settings with a specific name + * @param name Identifier for this channel selection (e.g., "top", "bottom") + * @param channels List of selected channel indices + */ + protected void saveNamedChannels(String name, List channels) { + widgetSettings.setNamedChannels(name, channels); + println(widgetTitle + ": Saved " + channels.size() + " channels for " + name); + } + + /** + * Apply saved named channel selection to a channel select component + * @param name Identifier for the channel selection + * @param channelSelect The channel select component to update + * @return true if channels were loaded and applied, false otherwise + */ + protected boolean applyNamedChannels(String name, ExGChannelSelect channelSelect) { + List savedChannels = widgetSettings.getNamedChannels(name); + if (!savedChannels.isEmpty()) { + channelSelect.updateChannelSelection(savedChannels); + return true; + } + return false; + } + + /** + * Get the list of active channels for a named selection from settings + * @param name Identifier for the channel selection + * @return List of active channel indices, or empty list if none are saved + */ + protected List getNamedChannels(String name) { + return widgetSettings.getNamedChannels(name); + } + + /** + * Check if a named channel selection is defined in settings + * @param name Identifier for the channel selection + * @return true if the named channel selection is defined, false otherwise + */ + protected boolean hasNamedChannels(String name) { + return widgetSettings.hasNamedChannels(name); + } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/OpenBCI_GUI/WidgetSettings.pde b/OpenBCI_GUI/WidgetSettings.pde index b6310574e..a05bbac71 100644 --- a/OpenBCI_GUI/WidgetSettings.pde +++ b/OpenBCI_GUI/WidgetSettings.pde @@ -124,6 +124,41 @@ class WidgetSettings { !channelSettings.get(KEY_ACTIVE_CHANNELS).isEmpty(); } + /** + * Store active channels with a specific name identifier + * @param name Identifier for this channel selection (e.g., "top", "bottom") + * @param channels List of selected channel indices + * @return this WidgetSettings instance for method chaining + */ + public WidgetSettings setNamedChannels(String name, List channels) { + // Create a copy to prevent external modification + channelSettings.put(name, new ArrayList(channels)); + return this; + } + + /** + * Get active channels for a specific named selection + * @param name Identifier for the channel selection + * @return List of selected channel indices or empty list if not found + */ + public List getNamedChannels(String name) { + if (channelSettings.containsKey(name)) { + // Return a copy to prevent external modification + return new ArrayList(channelSettings.get(name)); + } + return new ArrayList(); // Empty list if not found + } + + /** + * Check if a named channel selection exists + * @param name Identifier for the channel selection + * @return true if the named selection exists, false otherwise + */ + public boolean hasNamedChannels(String name) { + return channelSettings.containsKey(name) && + !channelSettings.get(name).isEmpty(); + } + // // OTHER SETTINGS // From 8a7e69add504aa227bd03c1327ddb3e3d1e12516 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 24 Apr 2025 17:36:29 -0500 Subject: [PATCH 23/29] Refactor Spectrogram Widget for readability and maintainability --- OpenBCI_GUI/W_Spectrogram.pde | 479 +++++++++++++++++++--------------- 1 file changed, 271 insertions(+), 208 deletions(-) diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index a4f833021..4715a5375 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -9,39 +9,39 @@ ////////////////////////////////////////////////////// class W_Spectrogram extends WidgetWithSettings { - public ExGChannelSelect spectChanSelectTop; - public ExGChannelSelect spectChanSelectBot; + private ExGChannelSelect spectChanSelectTop; + private ExGChannelSelect spectChanSelectBot; private boolean chanSelectWasOpen = false; - List cp5ElementsToCheck; - - int xPos = 0; - int hueLimit = 160; - - PImage dataImg; - int dataImageW = 1800; - int dataImageH = 200; - int prevW = 0; - int prevH = 0; - float scaledWidth; - float scaledHeight; - int graphX = 0; - int graphY = 0; - int graphW = 0; - int graphH = 0; - int midLineY = 0; + private List cp5ElementsToCheck; + + private int xPos = 0; + private int hueLimit = 160; + + private PImage dataImg; + private int dataImageW = 1800; + private int dataImageH = 200; + private int prevW = 0; + private int prevH = 0; + private float scaledWidth; + private float scaledHeight; + private int graphX = 0; + private int graphY = 0; + private int graphW = 0; + private int graphH = 0; + private int midLineY = 0; private int lastShift = 0; private int scrollSpeed = 25; // == 40Hz private boolean wasRunning = false; - int paddingLeft = 54; - int paddingRight = 26; - int paddingTop = 8; - int paddingBottom = 50; - StringList horizontalAxisLabelStrings; + private int paddingLeft = 54; + private int paddingRight = 26; + private int paddingTop = 8; + private int paddingBottom = 50; + private StringList horizontalAxisLabelStrings; - float[] topFFTAvg; - float[] botFFTAvg; + private float[] topFFTAvg; + private float[] botFFTAvg; W_Spectrogram() { super(); @@ -76,17 +76,7 @@ class W_Spectrogram extends WidgetWithSettings { initDropdown(SpectrogramWindowSize.class, "spectrogramWindowDropdown", "Window"); initDropdown(FFTLogLin.class, "spectrogramLogLinDropdown", "Log/Lin"); - spectChanSelectTop = new DualExGChannelSelect(ourApplet, x, y, w, navH, true); - spectChanSelectBot = new DualExGChannelSelect(ourApplet, x, y + navH, w, navH, false); - activateDefaultChannels(); - - // Save both channel selections with unique identifiers - saveNamedChannels("top", spectChanSelectTop.getActiveChannels()); - saveNamedChannels("bottom", spectChanSelectBot.getActiveChannels()); - - cp5ElementsToCheck = new ArrayList(); - cp5ElementsToCheck.addAll(spectChanSelectTop.getCp5ElementsForOverlapCheck()); - cp5ElementsToCheck.addAll(spectChanSelectBot.getCp5ElementsForOverlapCheck()); + initializeUI(); } @Override @@ -96,7 +86,10 @@ class W_Spectrogram extends WidgetWithSettings { updateDropdownLabel(FFTLogLin.class, "spectrogramLogLinDropdown"); applyMaxFrequency(); applyWindowSize(); - + applyChannelSettings(); + } + + private void applyChannelSettings() { // Apply saved channel selections if available if (hasNamedChannels("top")) { applyNamedChannels("top", spectChanSelectTop); @@ -110,46 +103,22 @@ class W_Spectrogram extends WidgetWithSettings { @Override protected void updateChannelSettings() { // Save current channel selections before saving settings + saveChannelSettings(); + } + + private void saveChannelSettings() { saveNamedChannels("top", spectChanSelectTop.getActiveChannels()); saveNamedChannels("bottom", spectChanSelectBot.getActiveChannels()); } - void update(){ + @Override + public void update(){ super.update(); //Update channel checkboxes, active channels, and position - spectChanSelectTop.update(x, y, w); - int chanSelectBotYOffset; - chanSelectBotYOffset = navH; - spectChanSelectBot.update(x, y + chanSelectBotYOffset, w); - - //Let the top channel select open the bottom one also so we can open both with 1 button - if (chanSelectWasOpen != spectChanSelectTop.isVisible()) { - spectChanSelectBot.setIsVisible(spectChanSelectTop.isVisible()); - chanSelectWasOpen = spectChanSelectTop.isVisible(); - } - - //Allow spectrogram to flex size and position depending on if the channel select is open - flexSpectrogramSizeAndPosition(); - - if (spectChanSelectTop.isVisible()) { - lockElementsOnOverlapCheck(cp5ElementsToCheck); - } + updateUIState(); - if (currentBoard.isStreaming()) { - //Make sure we are always draw new pixels on the right - xPos = dataImg.width - 1; - //Fetch/calculate the time strings for the horizontal axis ticks - horizontalAxisLabelStrings.clear(); - horizontalAxisLabelStrings = fetchTimeStrings(); - } - - //State change check - if (currentBoard.isStreaming() && !wasRunning) { - onStartRunning(); - } else if (!currentBoard.isStreaming() && wasRunning) { - onStopRunning(); - } + checkBoardStreamingState(); } private void onStartRunning() { @@ -161,97 +130,109 @@ class W_Spectrogram extends WidgetWithSettings { wasRunning = false; } - public void draw(){ + @Override + public void draw() { super.draw(); - - //put your code here... //remember to refer to x,y,w,h which are the positioning variables of the Widget class - //Scale the dataImage to fit in inside the widget float scaleW = float(graphW) / dataImageW; float scaleH = float(graphH) / dataImageH; + // Draw black background + drawBackground(); + + // Update spectrogram data if streaming + if (currentBoard.isStreaming()) { + updateSpectrogramData(); + } + + // Display the spectrogram image + displaySpectrogramImage(scaleW, scaleH); + + // Draw UI elements + spectChanSelectTop.draw(); + spectChanSelectBot.draw(); + drawAxes(scaleW, scaleH); + drawCenterLine(); + } + + private void drawBackground() { pushStyle(); fill(0); - rect(x, y, w, h); //draw a black background for the widget + rect(x, y, w, h); popStyle(); + } - //draw the spectrogram if the widget is open, and update pixels if board is streaming data - if (currentBoard.isStreaming()) { - pushStyle(); - - dataImg.loadPixels(); - - FFTLogLin logLin = widgetSettings.get(FFTLogLin.class); - - //Shift all pixels to the left! (every scrollspeed ms) - if(millis() - lastShift > scrollSpeed) { - for (int r = 0; r < dataImg.height; r++) { - if (r != 0) { - arrayCopy(dataImg.pixels, dataImg.width * r, dataImg.pixels, dataImg.width * r - 1, dataImg.width); - } else { - //When there would be an ArrayOutOfBoundsException, account for it! - arrayCopy(dataImg.pixels, dataImg.width * (r + 1), dataImg.pixels, r * dataImg.width, dataImg.width); - } - } + private void displaySpectrogramImage(float scaleW, float scaleH) { + pushMatrix(); + translate(graphX, graphY); + scale(scaleW, scaleH); + image(dataImg, 0, 0); + popMatrix(); + } - lastShift += scrollSpeed; - } - //for (int i = 0; i < fftLin_L.specSize() - 80; i++) { - for (int i = 0; i <= dataImg.height/2; i++) { - //LEFT SPECTROGRAM ON TOP - float hueValue = hueLimit - map((fftAvgs(spectChanSelectTop.getActiveChannels(), i)*32), 0, 256, 0, hueLimit); - if (logLin == FFTLogLin.LOG) { - hueValue = map(log10(hueValue), 0, 2, 0, hueLimit); - } - // colorMode is HSB, the range for hue is 256, for saturation is 100, brightness is 100. - colorMode(HSB, 256, 100, 100); - // color for stroke is specified as hue, saturation, brightness. - stroke(int(hueValue), 100, 80); - // plot a point using the specified stroke - //point(xPos, i); - int loc = xPos + ((dataImg.height/2 - i) * dataImg.width); - if (loc >= dataImg.width * dataImg.height) loc = dataImg.width * dataImg.height - 1; - try { - dataImg.pixels[loc] = color(int(hueValue), 100, 80); - } catch (Exception e) { - println("Major drawing error Spectrogram Left image!"); - } + private void updateSpectrogramData() { + pushStyle(); + dataImg.loadPixels(); + + // Shift pixels to the left if needed + shiftPixelsLeft(); + + // Calculate and draw new data points + drawSpectrogramPoints(); + + dataImg.updatePixels(); + popStyle(); + } - //RIGHT SPECTROGRAM ON BOTTOM - hueValue = hueLimit - map((fftAvgs(spectChanSelectBot.getActiveChannels(), i)*32), 0, 256, 0, hueLimit); - if (logLin == FFTLogLin.LOG) { - hueValue = map(log10(hueValue), 0, 2, 0, hueLimit); - } - // colorMode is HSB, the range for hue is 256, for saturation is 100, brightness is 100. - colorMode(HSB, 256, 100, 100); - // color for stroke is specified as hue, saturation, brightness. - stroke(int(hueValue), 100, 80); - int y_offset = -1; - // Pixel = X + ((Y + Height/2) * Width) - loc = xPos + ((i + dataImg.height/2 + y_offset) * dataImg.width); - if (loc >= dataImg.width * dataImg.height) loc = dataImg.width * dataImg.height - 1; - try { - dataImg.pixels[loc] = color(int(hueValue), 100, 80); - } catch (Exception e) { - println("Major drawing error Spectrogram Right image!"); + private void shiftPixelsLeft() { + if (millis() - lastShift > scrollSpeed) { + for (int r = 0; r < dataImg.height; r++) { + if (r != 0) { + arrayCopy(dataImg.pixels, dataImg.width * r, dataImg.pixels, dataImg.width * r - 1, dataImg.width); + } else { + arrayCopy(dataImg.pixels, dataImg.width * (r + 1), dataImg.pixels, r * dataImg.width, dataImg.width); } } - dataImg.updatePixels(); - popStyle(); + lastShift += scrollSpeed; } + } + + private void drawSpectrogramPoints() { + FFTLogLin logLin = widgetSettings.get(FFTLogLin.class); - pushMatrix(); - translate(graphX, graphY); - scale(scaleW, scaleH); - image(dataImg, 0, 0); - popMatrix(); + for (int i = 0; i <= dataImg.height/2; i++) { + // Draw top spectrogram (left channels) + drawSpectrogramPoint(spectChanSelectTop.getActiveChannels(), i, dataImg.height/2 - i, logLin); + + // Draw bottom spectrogram (right channels) + int y_offset = -1; + drawSpectrogramPoint(spectChanSelectBot.getActiveChannels(), i, i + dataImg.height/2 + y_offset, logLin); + } + } - spectChanSelectTop.draw(); - spectChanSelectBot.draw(); - drawAxes(scaleW, scaleH); - drawCenterLine(); + private void drawSpectrogramPoint(List channels, int freqBand, int yPosition, FFTLogLin logLin) { + float hueValue = hueLimit - map((fftAvgs(channels, freqBand)*32), 0, 256, 0, hueLimit); + + if (logLin == FFTLogLin.LOG) { + hueValue = map(log10(hueValue), 0, 2, 0, hueLimit); + } + + colorMode(HSB, 256, 100, 100); + stroke(int(hueValue), 100, 80); + + int loc = xPos + (yPosition * dataImg.width); + if (loc >= dataImg.width * dataImg.height) { + loc = dataImg.width * dataImg.height - 1; + } + + try { + dataImg.pixels[loc] = color(int(hueValue), 100, 80); + } catch (Exception e) { + println("Major drawing error in Spectrogram at position: " + yPosition); + } } + @Override public void screenResized(){ super.screenResized(); @@ -263,90 +244,112 @@ class W_Spectrogram extends WidgetWithSettings { graphH = h - paddingBottom - paddingTop; } - void mousePressed(){ + @Override + public void mousePressed(){ super.mousePressed(); spectChanSelectTop.mousePressed(this.dropdownIsActive); //Calls channel select mousePressed and checks if clicked spectChanSelectBot.mousePressed(this.dropdownIsActive); } - void mouseReleased(){ + @Override + public void mouseReleased(){ super.mouseReleased(); + } + private void drawAxes(float scaledW, float scaledH) { + drawSpectrogramBorder(scaledW, scaledH); + drawHorizontalAxisAndLabels(scaledW, scaledH); + drawVerticalAxisAndLabels(scaledW, scaledH); + drawYAxisLabel(); + drawColorScaleReference(); } - void drawAxes(float scaledW, float scaledH) { - + private void drawSpectrogramBorder(float scaledW, float scaledH) { pushStyle(); - fill(255); - textSize(14); - //draw horizontal axis label - text("Time", x + w/2 - textWidth("Time")/3, y + h - 9); - noFill(); - stroke(255); - strokeWeight(2); - //draw rectangle around the spectrogram - rect(graphX, graphY, scaledW * dataImageW, scaledH * dataImageH); + fill(255); + textSize(14); + text("Time", x + w/2 - textWidth("Time")/3, y + h - 9); + noFill(); + stroke(255); + strokeWeight(2); + rect(graphX, graphY, scaledW * dataImageW, scaledH * dataImageH); popStyle(); + } + private void drawHorizontalAxisAndLabels(float scaledW, float scaledH) { pushStyle(); - //draw horizontal axis ticks from left to right - int tickMarkSize = 7; //in pixels - float horizontalAxisX = graphX; - float horizontalAxisY = graphY + scaledH * dataImageH; - stroke(255); - fill(255); - strokeWeight(2); - textSize(11); - SpectrogramWindowSize windowSize = widgetSettings.get(SpectrogramWindowSize.class); - int horizontalAxisDivCount = windowSize.getAxisLabels().length; - for (int i = 0; i < horizontalAxisDivCount; i++) { - float offset = scaledW * dataImageW * (float(i) / horizontalAxisDivCount); - line(horizontalAxisX + offset, horizontalAxisY, horizontalAxisX + offset, horizontalAxisY + tickMarkSize); - if (horizontalAxisLabelStrings.get(i) != null) { - text(horizontalAxisLabelStrings.get(i), horizontalAxisX + offset - (int)textWidth(horizontalAxisLabelStrings.get(i))/2, horizontalAxisY + tickMarkSize * 3); - } + int tickMarkSize = 7; + float horizontalAxisX = graphX; + float horizontalAxisY = graphY + scaledH * dataImageH; + stroke(255); + fill(255); + strokeWeight(2); + textSize(11); + + SpectrogramWindowSize windowSize = widgetSettings.get(SpectrogramWindowSize.class); + int horizontalAxisDivCount = windowSize.getAxisLabels().length; + + for (int i = 0; i < horizontalAxisDivCount; i++) { + float offset = scaledW * dataImageW * (float(i) / horizontalAxisDivCount); + line(horizontalAxisX + offset, horizontalAxisY, horizontalAxisX + offset, horizontalAxisY + tickMarkSize); + + if (horizontalAxisLabelStrings.get(i) != null) { + text(horizontalAxisLabelStrings.get(i), + horizontalAxisX + offset - (int)textWidth(horizontalAxisLabelStrings.get(i))/2, + horizontalAxisY + tickMarkSize * 3); } + } popStyle(); - + } + + private void drawYAxisLabel() { pushStyle(); - pushMatrix(); - rotate(radians(-90)); - textSize(14); - int yAxisLabelOffset = spectChanSelectTop.isVisible() ? (int)textWidth("Frequency (Hz)") / 4 : 0; - translate(-h/2 - textWidth("Frequency (Hz)")/4, 20); - fill(255); - // Draw y axis label only when channel select is not visible due to overlap - if (!spectChanSelectTop.isVisible()) { - text("Frequency (Hz)", -y - yAxisLabelOffset, x); - } - popMatrix(); + pushMatrix(); + rotate(radians(-90)); + textSize(14); + int yAxisLabelOffset = spectChanSelectTop.isVisible() ? (int)textWidth("Frequency (Hz)") / 4 : 0; + translate(-h/2 - textWidth("Frequency (Hz)")/4, 20); + fill(255); + + if (!spectChanSelectTop.isVisible()) { + text("Frequency (Hz)", -y - yAxisLabelOffset, x); + } + + popMatrix(); popStyle(); + } + private void drawVerticalAxisAndLabels(float scaledW, float scaledH) { pushStyle(); - //draw vertical axis ticks from top to bottom - float verticalAxisX = graphX; - float verticalAxisY = graphY; - stroke(255); - fill(255); - textSize(12); - strokeWeight(2); - SpectrogramMaxFrequency maxFrequency = widgetSettings.get(SpectrogramMaxFrequency.class); - int verticalAxisDivCount = maxFrequency.getAxisLabels().length - 1; - for (int i = 0; i < verticalAxisDivCount; i++) { - float offset = scaledH * dataImageH * (float(i) / verticalAxisDivCount); - //if (i <= verticalAxisDivCount/2) offset -= 2; - line(verticalAxisX, verticalAxisY + offset, verticalAxisX - tickMarkSize, verticalAxisY + offset); - if (maxFrequency.getAxisLabels()[i] == 0) midLineY = int(verticalAxisY + offset); - offset += paddingTop/2; - text(maxFrequency.getAxisLabels()[i], verticalAxisX - tickMarkSize*2 - textWidth(Integer.toString(maxFrequency.getAxisLabels()[i])), verticalAxisY + offset); + float verticalAxisX = graphX; + float verticalAxisY = graphY; + int tickMarkSize = 7; + stroke(255); + fill(255); + textSize(12); + strokeWeight(2); + + SpectrogramMaxFrequency maxFrequency = widgetSettings.get(SpectrogramMaxFrequency.class); + int verticalAxisDivCount = maxFrequency.getAxisLabels().length - 1; + + for (int i = 0; i < verticalAxisDivCount; i++) { + float offset = scaledH * dataImageH * (float(i) / verticalAxisDivCount); + line(verticalAxisX, verticalAxisY + offset, verticalAxisX - tickMarkSize, verticalAxisY + offset); + + if (maxFrequency.getAxisLabels()[i] == 0) { + midLineY = int(verticalAxisY + offset); } + + offset += paddingTop/2; + text(maxFrequency.getAxisLabels()[i], + verticalAxisX - tickMarkSize*2 - textWidth(Integer.toString(maxFrequency.getAxisLabels()[i])), + verticalAxisY + offset); + } popStyle(); - - drawColorScaleReference(); } - void drawCenterLine() { + private void drawCenterLine() { //draw a thick line down the middle to separate the two plots pushStyle(); stroke(255); @@ -355,7 +358,7 @@ class W_Spectrogram extends WidgetWithSettings { popStyle(); } - void drawColorScaleReference() { + private void drawColorScaleReference() { int colorScaleHeight = 128; //Dynamically scale the Log/Lin amplitude-to-color reference line. If it won't fit, don't draw it. if (graphH < colorScaleHeight) { @@ -383,7 +386,7 @@ class W_Spectrogram extends WidgetWithSettings { popStyle(); } - void activateDefaultChannels() { + private void activateDefaultChannels() { int[] topChansToActivate; int[] botChansToActivate; @@ -408,7 +411,7 @@ class W_Spectrogram extends WidgetWithSettings { } } - void flexSpectrogramSizeAndPosition() { + private void flexSpectrogramSizeAndPosition() { int flexHeight = spectChanSelectTop.getHeight() + spectChanSelectBot.getHeight(); if (spectChanSelectTop.isVisible()) { graphY = y + paddingTop + flexHeight; @@ -419,11 +422,11 @@ class W_Spectrogram extends WidgetWithSettings { } } - void setScrollSpeed(int i) { + public void setScrollSpeed(int i) { scrollSpeed = i; } - float fftAvgs(List _activeChan, int freqBand) { + private float fftAvgs(List _activeChan, int freqBand) { float sum = 0f; for (int i = 0; i < _activeChan.size(); i++) { sum += fftBuff[_activeChan.get(i)].getBand(freqBand); @@ -499,6 +502,66 @@ class W_Spectrogram extends WidgetWithSettings { horizontalAxisLabelStrings = fetchTimeStrings(); dataImg = createImage(dataImageW, dataImageH, RGB); } + + private void resetSpectrogramImage() { + // Create a new image with the current settings + dataImg = createImage(dataImageW, dataImageH, RGB); + } + + private void updateTimeAxisLabels() { + horizontalAxisLabelStrings.clear(); + horizontalAxisLabelStrings = fetchTimeStrings(); + } + + private void checkBoardStreamingState() { + if (currentBoard.isStreaming()) { + // Update position for new data points + xPos = dataImg.width - 1; + // Update time axis labels + updateTimeAxisLabels(); + } + + // State change detection + if (currentBoard.isStreaming() && !wasRunning) { + onStartRunning(); + } else if (!currentBoard.isStreaming() && wasRunning) { + onStopRunning(); + } + } + + private void initializeUI() { + spectChanSelectTop = new DualExGChannelSelect(ourApplet, x, y, w, navH, true); + spectChanSelectBot = new DualExGChannelSelect(ourApplet, x, y + navH, w, navH, false); + activateDefaultChannels(); + + cp5ElementsToCheck = new ArrayList(); + cp5ElementsToCheck.addAll(spectChanSelectTop.getCp5ElementsForOverlapCheck()); + cp5ElementsToCheck.addAll(spectChanSelectBot.getCp5ElementsForOverlapCheck()); + } + + private void updateUIState() { + spectChanSelectTop.update(x, y, w); + int chanSelectBotYOffset = navH; + spectChanSelectBot.update(x, y + chanSelectBotYOffset, w); + + // Synchronize visibility between top and bottom channel selectors + synchronizeChannelSelectors(); + + // Update flexible layout based on channel selector visibility + flexSpectrogramSizeAndPosition(); + + // Handle UI element overlap checking + if (spectChanSelectTop.isVisible()) { + lockElementsOnOverlapCheck(cp5ElementsToCheck); + } + } + + private void synchronizeChannelSelectors() { + if (chanSelectWasOpen != spectChanSelectTop.isVisible()) { + spectChanSelectBot.setIsVisible(spectChanSelectTop.isVisible()); + chanSelectWasOpen = spectChanSelectTop.isVisible(); + } + } }; public void spectrogramMaxFrequencyDropdown(int n) { From c3ab090a74c455dfb63de225707fdc16789031c4 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 24 Apr 2025 19:09:44 -0500 Subject: [PATCH 24/29] Add WidgetWithSettings to Marker widget --- OpenBCI_GUI/MarkerEnums.pde | 64 ++++++++++ OpenBCI_GUI/W_Marker.pde | 226 +++++++++++++++------------------ OpenBCI_GUI/W_Spectrogram.pde | 19 ++- OpenBCI_GUI/Widget.pde | 13 +- OpenBCI_GUI/WidgetSettings.pde | 4 +- 5 files changed, 188 insertions(+), 138 deletions(-) create mode 100644 OpenBCI_GUI/MarkerEnums.pde diff --git a/OpenBCI_GUI/MarkerEnums.pde b/OpenBCI_GUI/MarkerEnums.pde new file mode 100644 index 000000000..e80b592b3 --- /dev/null +++ b/OpenBCI_GUI/MarkerEnums.pde @@ -0,0 +1,64 @@ +public enum MarkerWindow implements IndexingInterface +{ + FIVE (0, 5, "5 sec"), + TEN (1, 10, "10 sec"), + TWENTY (2, 20, "20 sec"); + + private int index; + private int value; + private String label; + + MarkerWindow(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } +} + +public enum MarkerVertScale implements IndexingInterface +{ + AUTO (0, 0, "Auto"), + TWO (1, 2, "2"), + FOUR (2, 4, "4"), + EIGHT (3, 8, "8"), + TEN (4, 10, "10"), + TWENTY (6, 20, "20"); + + private int index; + private int value; + private String label; + + MarkerVertScale(int _index, int _value, String _label) { + this.index = _index; + this.value = _value; + this.label = _label; + } + + public int getValue() { + return value; + } + + @Override + public String getString() { + return label; + } + + @Override + public int getIndex() { + return index; + } +} diff --git a/OpenBCI_GUI/W_Marker.pde b/OpenBCI_GUI/W_Marker.pde index 58a849630..9aa472ed9 100644 --- a/OpenBCI_GUI/W_Marker.pde +++ b/OpenBCI_GUI/W_Marker.pde @@ -8,7 +8,7 @@ // // ////////////////////////////////////////////////////// -class W_Marker extends Widget { +class W_Marker extends WidgetWithSettings { private ControlP5 localCP5; private List cp5ElementsToCheckForOverlap; @@ -24,8 +24,10 @@ class W_Marker extends Widget { private Textfield markerReceiveIPTextfield; private Textfield markerReceivePortTextfield; - private String markerReceiveIP = "127.0.0.1"; - private int markerReceivePort = 12340; + private final String KEY_MARKER_RECEIVE_IP = "markerReceiveIPTextfield"; + private final String KEY_MARKER_RECEIVE_PORT = "markerReceivePortTextfield"; + private final String DEFAULT_RECEIVER_IP = "127.0.0.1"; + private final int DEFAULT_RECEIVER_PORT = 12340; private final int MARKER_RECEIVE_TEXTFIELD_WIDTH = 108; private final int MARKER_RECEIVE_TEXTFIELD_HEIGHT = 22; @@ -41,9 +43,6 @@ class W_Marker extends Widget { private int PAD_FIVE = 5; private int GRAPH_PADDING = 30; - private MarkerVertScale markerVertScale = MarkerVertScale.EIGHT; - private MarkerWindow markerWindow = MarkerWindow.FIVE; - W_Marker() { super(); widgetTitle = "Marker"; @@ -54,14 +53,10 @@ class W_Marker extends Widget { localCP5.setAutoDraw(false); createMarkerButtons(); - - List verticalScaleList = EnumHelper.getEnumStrings(MarkerVertScale.class); - List windowList = EnumHelper.getEnumStrings(MarkerWindow.class); - - addDropdown("markerVertScaleDropdown", "Vert Scale", verticalScaleList, markerVertScale.getIndex()); - addDropdown("markerWindowDropdown", "Window", windowList, markerWindow.getIndex()); updateGraphDims(); + MarkerWindow markerWindow = widgetSettings.get(MarkerWindow.class); + MarkerVertScale markerVertScale = widgetSettings.get(MarkerVertScale.class); markerBar = new MarkerBar(ourApplet, MAX_NUMBER_OF_MARKER_BUTTONS, markerWindow.getValue(), markerVertScale.getValue(), graphX, graphY, graphW, graphH); grid = new Grid(MARKER_UI_GRID_ROWS, MARKER_UI_GRID_COLUMNS, MARKER_UI_GRID_CELL_HEIGHT); @@ -83,6 +78,57 @@ class W_Marker extends Widget { cp5ElementsToCheckForOverlap.add(markerReceiveToggle); } + @Override + protected void initWidgetSettings() { + super.initWidgetSettings(); + widgetSettings.set(MarkerVertScale.class, MarkerVertScale.EIGHT) + .set(MarkerWindow.class, MarkerWindow.FIVE) + .setObject(KEY_MARKER_RECEIVE_IP, DEFAULT_RECEIVER_IP) + .setObject(KEY_MARKER_RECEIVE_PORT, DEFAULT_RECEIVER_PORT) + .saveDefaults(); + + initDropdown(MarkerVertScale.class, "markerVerticalScaleDropdown", "Vert Scale"); + initDropdown(MarkerWindow.class, "markerWindowDropdown", "Window"); + } + + @Override + protected void applySettings() { + updateDropdownLabel(MarkerVertScale.class, "markerVerticalScaleDropdown"); + updateDropdownLabel(MarkerWindow.class, "markerWindowDropdown"); + + applyVerticalScale(); + applyWindow(); + + String ipValue = widgetSettings.getObject(KEY_MARKER_RECEIVE_IP, DEFAULT_RECEIVER_IP).toString(); + String portValue = widgetSettings.getObject(KEY_MARKER_RECEIVE_PORT, DEFAULT_RECEIVER_PORT).toString(); + + markerReceiveIPTextfield.setText(ipValue); + markerReceivePortTextfield.setText(portValue); + } + + @Override + protected void saveSettings() { + // Call the parent method to handle default saving behavior + super.saveSettings(); + + // Save our marker-specific settings + saveMarkerSettings(); + } + + private void saveMarkerSettings() { + // Get the current values from textfields + String currentIP = markerReceiveIPTextfield.getText(); + String currentPort = markerReceivePortTextfield.getText(); + + // Clean up the values + currentIP = getIpAddrFromStr(currentIP); + Integer currentPortInt = Integer.parseInt(dropNonPrintableChars(currentPort)); + + // Save values to widget settings + widgetSettings.setObject(KEY_MARKER_RECEIVE_IP, currentIP); + widgetSettings.setObject(KEY_MARKER_RECEIVE_PORT, currentPortInt); + } + public void update(){ super.update(); markerBar.update(); @@ -91,7 +137,6 @@ class W_Marker extends Widget { textfieldUpdateHelper.checkTextfield(markerReceivePortTextfield); lockElementsOnOverlapCheck(cp5ElementsToCheckForOverlap); - } public void draw(){ @@ -210,8 +255,8 @@ class W_Marker extends Widget { } private void createMarkerReceiveUI() { - markerReceiveIPTextfield = createTextfield("markerReceiveIPTextfield", markerReceiveIP); - markerReceivePortTextfield = createTextfield("markerReceivePortTextfield", Integer.toString(markerReceivePort)); + markerReceiveIPTextfield = createTextfield(KEY_MARKER_RECEIVE_IP, DEFAULT_RECEIVER_IP); + markerReceivePortTextfield = createTextfield(KEY_MARKER_RECEIVE_PORT, Integer.toString(DEFAULT_RECEIVER_PORT)); createMarkerReceiveToggle(); } @@ -244,6 +289,8 @@ class W_Marker extends Widget { public void controlEvent(CallbackEvent theEvent) { if (theEvent.getAction() == ControlP5.ACTION_BROADCAST && myTextfield.getText().equals("")) { resetMarkerReceiveTextfield(myTextfield); + } else if (theEvent.getAction() == ControlP5.ACTION_BROADCAST) { + saveMarkerSettings(); } } }); @@ -253,16 +300,17 @@ class W_Marker extends Widget { if (!myTextfield.isActive() && myTextfield.getText().equals("")) { resetMarkerReceiveTextfield(myTextfield); } + saveMarkerSettings(); } }); return myTextfield; } private void resetMarkerReceiveTextfield(Textfield tf) { - if (tf.getName().equals("markerReceiveIPTextfield")) { - tf.setText(markerReceiveIP); - } else if (tf.getName().equals("markerReceivePortTextfield")) { - tf.setText(Integer.toString(markerReceivePort)); + if (tf.getName().equals(KEY_MARKER_RECEIVE_IP)) { + tf.setText(DEFAULT_RECEIVER_IP); + } else if (tf.getName().equals(KEY_MARKER_RECEIVE_PORT)) { + tf.setText(Integer.toString(DEFAULT_RECEIVER_PORT)); } } @@ -291,24 +339,24 @@ class W_Marker extends Widget { } private void initUdpMarkerReceiver() { - markerReceiveIP = getIpAddrFromStr(markerReceiveIPTextfield.getText()); - markerReceivePort = Integer.parseInt(dropNonPrintableChars(markerReceivePortTextfield.getText())); + String currentIP = getIpAddrFromStr(markerReceiveIPTextfield.getText()); + Integer currentPort = Integer.parseInt(dropNonPrintableChars(markerReceivePortTextfield.getText())); disposeUdpMarkerReceiver(); - udpReceiver = new UDP(ourApplet, markerReceivePort, markerReceiveIP); + udpReceiver = new UDP(ourApplet, currentPort, currentIP); udpReceiver.listen(true); udpReceiver.broadcast(false); udpReceiver.log(false); udpReceiver.setReceiveHandler("receiveMarkerViaUdp"); - outputSuccess("Marker Widget: Listening for markers on " + markerReceiveIP + ":" + markerReceivePort); + outputSuccess("Marker Widget: Listening for markers on " + currentIP + ":" + currentPort); } public void disposeUdpMarkerReceiver() { if (udpReceiver != null) { udpReceiver.close(); udpReceiver.dispose(); - println("Marker Widget: Stopped listening for markers on " + markerReceiveIP + ":" + markerReceivePort); + println("Marker Widget: Stopped listening for markers"); } } @@ -333,32 +381,46 @@ class W_Marker extends Widget { } public void setMarkerWindow(int n) { - markerWindow = markerWindow.values()[n]; - markerBar.adjustTimeAxis(markerWindow.getValue()); + widgetSettings.setByIndex(MarkerWindow.class, n); + applyWindow(); } - public void setMarkerVertScale(int n) { - markerVertScale = markerVertScale.values()[n]; - markerBar.adjustYAxis(markerVertScale.getValue()); + public void setMarkerVerticalScale(int n) { + widgetSettings.setByIndex(MarkerVertScale.class, n); + applyVerticalScale(); } - public MarkerWindow getMarkerWindow() { - return markerWindow; + private void applyWindow() { + int markerWindowValue = widgetSettings.get(MarkerWindow.class).getValue(); + markerBar.adjustTimeAxis(markerWindowValue); } - public MarkerVertScale getMarkerVertScale() { - return markerVertScale; + private void applyVerticalScale() { + int markerVertScaleValue = widgetSettings.get(MarkerVertScale.class).getValue(); + markerBar.adjustYAxis(markerVertScaleValue); } +}; - public String getMarkerReceiveIP() { - return getIpAddrFromStr(markerReceiveIPTextfield.getText()); - } - public String getMarkerReceivePort() { - return dropNonPrintableChars(markerReceivePortTextfield.getText()); - } +//The following global functions are used by the Marker widget dropdowns. This method is the least amount of code. +public void markerWindowDropdown(int n) { + W_Marker markerWidget = (W_Marker) widgetManager.getWidget("W_Marker"); + markerWidget.setMarkerWindow(n); +} -}; +public void markerVerticalScaleDropdown(int n) { + W_Marker markerWidget = (W_Marker) widgetManager.getWidget("W_Marker"); + markerWidget.setMarkerVerticalScale(n); +} + +//Custom UDP receive handler for receiving markers from external sources +public void receiveMarkerViaUdp( byte[] data, String ip, int port ) { + double markerValue = convertByteArrayToDouble(data); + //String message = Double.toString(markerValue); + //println( "received: \""+message+"\" from "+ip+" on port "+port ); + W_Marker markerWidget = (W_Marker) widgetManager.getWidget("W_Marker"); + markerWidget.insertMarkerFromExternal(markerValue); +} //This class contains the time series plot for displaying the markers over time class MarkerBar { @@ -513,87 +575,3 @@ class MarkerBar { } }; -public enum MarkerWindow implements IndexingInterface -{ - FIVE (0, 5, "5 sec"), - TEN (1, 10, "10 sec"), - TWENTY (2, 20, "20 sec"); - - private int index; - private int value; - private String label; - - MarkerWindow(int _index, int _value, String _label) { - this.index = _index; - this.value = _value; - this.label = _label; - } - - public int getValue() { - return value; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } -} - -public enum MarkerVertScale implements IndexingInterface -{ - AUTO (0, 0, "Auto"), - TWO (1, 2, "2"), - FOUR (2, 4, "4"), - EIGHT (3, 8, "8"), - TEN (4, 10, "10"), - TWENTY (6, 20, "20"); - - private int index; - private int value; - private String label; - - MarkerVertScale(int _index, int _value, String _label) { - this.index = _index; - this.value = _value; - this.label = _label; - } - - public int getValue() { - return value; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } -} - -//The following global functions are used by the Marker widget dropdowns. This method is the least amount of code. -public void markerWindowDropdown(int n) { - W_Marker markerWidget = (W_Marker) widgetManager.getWidget("W_Marker"); - markerWidget.setMarkerWindow(n); -} - -public void markerVertScaleDropdown(int n) { - W_Marker markerWidget = (W_Marker) widgetManager.getWidget("W_Marker"); - markerWidget.setMarkerVertScale(n); -} - -//Custom UDP receive handler for receiving markers from external sources -public void receiveMarkerViaUdp( byte[] data, String ip, int port ) { - double markerValue = convertByteArrayToDouble(data); - //String message = Double.toString(markerValue); - //println( "received: \""+message+"\" from "+ip+" on port "+port ); - W_Marker markerWidget = (W_Marker) widgetManager.getWidget("W_Marker"); - markerWidget.insertMarkerFromExternal(markerValue); -} diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index 4715a5375..c3624575e 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -76,7 +76,14 @@ class W_Spectrogram extends WidgetWithSettings { initDropdown(SpectrogramWindowSize.class, "spectrogramWindowDropdown", "Window"); initDropdown(FFTLogLin.class, "spectrogramLogLinDropdown", "Log/Lin"); - initializeUI(); + spectChanSelectTop = new DualExGChannelSelect(ourApplet, x, y, w, navH, true); + spectChanSelectBot = new DualExGChannelSelect(ourApplet, x, y + navH, w, navH, false); + activateDefaultChannels(); + updateChannelSettings(); + + cp5ElementsToCheck = new ArrayList(); + cp5ElementsToCheck.addAll(spectChanSelectTop.getCp5ElementsForOverlapCheck()); + cp5ElementsToCheck.addAll(spectChanSelectBot.getCp5ElementsForOverlapCheck()); } @Override @@ -529,16 +536,6 @@ class W_Spectrogram extends WidgetWithSettings { } } - private void initializeUI() { - spectChanSelectTop = new DualExGChannelSelect(ourApplet, x, y, w, navH, true); - spectChanSelectBot = new DualExGChannelSelect(ourApplet, x, y + navH, w, navH, false); - activateDefaultChannels(); - - cp5ElementsToCheck = new ArrayList(); - cp5ElementsToCheck.addAll(spectChanSelectTop.getCp5ElementsForOverlapCheck()); - cp5ElementsToCheck.addAll(spectChanSelectBot.getCp5ElementsForOverlapCheck()); - } - private void updateUIState() { spectChanSelectTop.update(x, y, w); int chanSelectBotYOffset = navH; diff --git a/OpenBCI_GUI/Widget.pde b/OpenBCI_GUI/Widget.pde index c2a3ef4e5..4a0abafc3 100644 --- a/OpenBCI_GUI/Widget.pde +++ b/OpenBCI_GUI/Widget.pde @@ -346,8 +346,8 @@ abstract class WidgetWithSettings extends Widget { * @return JSON representation of settings */ public String settingsToJSON() { - // If the widget has a channel selector, save its current state - updateChannelSettings(); + // Call saveSettings to ensure all widget settings are up-to-date before serializing + saveSettings(); return widgetSettings.toJSON(); } @@ -480,6 +480,15 @@ abstract class WidgetWithSettings extends Widget { protected boolean hasNamedChannels(String name) { return widgetSettings.hasNamedChannels(name); } + + /** + * Save settings before serializing to JSON + * Default implementation - for channels only + * Child classes can override this to save additional settings + */ + protected void saveSettings() { + updateChannelSettings(); + } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/OpenBCI_GUI/WidgetSettings.pde b/OpenBCI_GUI/WidgetSettings.pde index a05bbac71..2a0236337 100644 --- a/OpenBCI_GUI/WidgetSettings.pde +++ b/OpenBCI_GUI/WidgetSettings.pde @@ -332,8 +332,10 @@ class WidgetSettings { othersJson.setFloat(key, (Float)value); } else if (value instanceof Boolean) { othersJson.setBoolean(key, (Boolean)value); + } else { + println("WARNING: Couldn't save setting '" + key + "' with value type " + + (value != null ? value.getClass().getName() : "null")); } - // Skip complex types that can't be directly serialized } if (othersJson.size() > 0) { From ad4621f6aca5d2e9df8cd0aa6c315fa12086d8d4 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Thu, 24 Apr 2025 19:37:07 -0500 Subject: [PATCH 25/29] Add WidgetWithSettings to Focus widget --- OpenBCI_GUI/W_Focus.pde | 122 ++++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 47 deletions(-) diff --git a/OpenBCI_GUI/W_Focus.pde b/OpenBCI_GUI/W_Focus.pde index 5f03b498c..fae7599eb 100644 --- a/OpenBCI_GUI/W_Focus.pde +++ b/OpenBCI_GUI/W_Focus.pde @@ -21,7 +21,8 @@ import brainflow.DataFilter; import brainflow.LogLevels; import brainflow.MLModel; -class W_Focus extends Widget { +class W_Focus extends WidgetWithSettings { + private ExGChannelSelect focusChanSelect; private boolean prevChanSelectIsVisible = false; private AuditoryNeurofeedback auditoryNeurofeedback; @@ -42,11 +43,6 @@ class W_Focus extends Widget { private FifoChannelBar focusBar; private float focusBarHardYAxisLimit = 1.05f; //Provide slight "breathing room" to avoid GPlot error when metric value == 1.0 - private FocusXLim xLimit = FocusXLim.TEN; - private FocusMetric focusMetric = FocusMetric.RELAXATION; - private FocusClassifier focusClassifier = FocusClassifier.REGRESSION; - private FocusThreshold focusThreshold = FocusThreshold.EIGHT_TENTHS; - private FocusColors focusColors = FocusColors.GREEN; private int[] exgChannels; private int channelCount; @@ -61,18 +57,12 @@ class W_Focus extends Widget { private final int GRAPH_PADDING = 30; private color cBack, cDark, cMark, cFocus, cWave, cPanel; - List cp5ElementsToCheck = new ArrayList(); + List cp5ElementsToCheck; W_Focus() { super(); widgetTitle = "Focus"; - - //Add channel select dropdown to this widget - focusChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); - focusChanSelect.activateAllButtons(); - cp5ElementsToCheck.addAll(focusChanSelect.getCp5ElementsForOverlapCheck()); - auditoryNeurofeedback = new AuditoryNeurofeedback(x + PAD_FIVE, y + PAD_FIVE, w/2 - PAD_FIVE*2, navBarHeight/2); cp5ElementsToCheck.add((controlP5.Controller)auditoryNeurofeedback.startStopButton); cp5ElementsToCheck.add((controlP5.Controller)auditoryNeurofeedback.modeButton); @@ -83,15 +73,6 @@ class W_Focus extends Widget { // initialize graphics parameters onColorChange(); - - List metricList = EnumHelper.getEnumStrings(FocusMetric.class); - List thresholdList = EnumHelper.getEnumStrings(FocusThreshold.class); - List xLimitList = EnumHelper.getEnumStrings(FocusXLim.class); - - dropdownWidth = 60; //Override the default dropdown width for this widget - addDropdown("focusMetricDropdown", "Metric", metricList, focusMetric.getIndex()); - addDropdown("focusThresholdDropdown", "Threshold", thresholdList, focusThreshold.getIndex()); - addDropdown("focusWindowDropdown", "Window", xLimitList, xLimit.getIndex()); //Create data table dataGrid = new Grid(NUM_TABLE_ROWS, NUM_TABLE_COLUMNS, cellHeight); @@ -106,11 +87,58 @@ class W_Focus extends Widget { //create our focus graph updateGraphDims(); - focusBar = new FifoChannelBar(ourApplet, "Metric Value", xLimit.getValue(), focusBarHardYAxisLimit, graphX, graphY, graphW, graphH, ACCEL_X_COLOR, FocusXLim.TWENTY.getValue()); + int xLimitValue = widgetSettings.get(FocusXLim.class).getValue(); + focusBar = new FifoChannelBar(ourApplet, "Metric Value", xLimitValue, focusBarHardYAxisLimit, graphX, graphY, graphW, graphH, ACCEL_X_COLOR, FocusXLim.TWENTY.getValue()); initBrainFlowMetric(); } + @Override + protected void initWidgetSettings() { + super.initWidgetSettings(); + + widgetSettings.set(FocusXLim.class, FocusXLim.TEN) + .set(FocusMetric.class, FocusMetric.RELAXATION) + .set(FocusClassifier.class, FocusClassifier.REGRESSION) + .set(FocusThreshold.class, FocusThreshold.EIGHT_TENTHS) + .set(FocusColors.class, FocusColors.GREEN); + + dropdownWidth = 60; //Override the default dropdown width for this widget + initDropdown(FocusMetric.class, "focusMetricDropdown", "Metric"); + initDropdown(FocusThreshold.class, "focusThresholdDropdown", "Threshold"); + initDropdown(FocusXLim.class, "focusWindowDropdown", "Window"); + + //Add channel select dropdown to this widget + cp5ElementsToCheck = new ArrayList(); + focusChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); + focusChanSelect.activateAllButtons(); + saveActiveChannels(focusChanSelect.getActiveChannels()); + cp5ElementsToCheck.addAll(focusChanSelect.getCp5ElementsForOverlapCheck()); + + widgetSettings.saveDefaults(); + } + + @Override + protected void applySettings() { + //Apply settings to dropdowns + updateDropdownLabel(FocusXLim.class, "focusWindowDropdown"); + updateDropdownLabel(FocusMetric.class, "focusMetricDropdown"); + updateDropdownLabel(FocusThreshold.class, "focusThresholdDropdown"); + applyHorizontalScale(); + initBrainFlowMetric(); + + //Apply settings to channel select dropdown + applyActiveChannels(focusChanSelect); + } + + @Override + protected void updateChannelSettings() { + //Save active channels to settings + if (focusChanSelect != null) { + saveActiveChannels(focusChanSelect.getActiveChannels()); + } + } + public void update() { super.update(); @@ -228,7 +256,8 @@ class W_Focus extends Widget { //Returns a metric value from 0. to 1. When there is an error, returns -1. private double updateFocusState() { try { - int windowSize = currentBoard.getSampleRate() * xLimit.getValue(); + int xLimitValue = widgetSettings.get(FocusXLim.class).getValue(); + int windowSize = currentBoard.getSampleRate() * xLimitValue; // getData in GUI returns data in shape ndatapoints x nchannels, in BrainFlow its transposed List currentData = currentBoard.getData(windowSize); @@ -289,6 +318,7 @@ class W_Focus extends Widget { strokeColor = cDark; sb.append("Not "); } + FocusMetric focusMetric = widgetSettings.get(FocusMetric.class); sb.append(focusMetric.getIdealStateString()); //Draw status graphic pushStyle(); @@ -304,6 +334,11 @@ class W_Focus extends Widget { } private void initBrainFlowMetric() { + if (mlModel != null) { + endSession(); + } + FocusMetric focusMetric = widgetSettings.get(FocusMetric.class); + FocusClassifier focusClassifier = widgetSettings.get(FocusClassifier.class); BrainFlowModelParams modelParams = new BrainFlowModelParams( focusMetric.getMetric().get_code(), focusClassifier.getClassifier().get_code() @@ -326,6 +361,7 @@ class W_Focus extends Widget { } private void onColorChange() { + FocusColors focusColors = widgetSettings.get(FocusColors.class); switch(focusColors) { case GREEN: cBack = #ffffff; //white @@ -362,25 +398,28 @@ class W_Focus extends Widget { updateAuditoryNeurofeedbackPosition(); } - public void setFocusHorizScale(int n) { - xLimit = xLimit.values()[n]; - focusBar.adjustTimeAxis(xLimit.getValue()); + public void setFocusHorizontalScale(int n) { + widgetSettings.setByIndex(FocusXLim.class, n); + applyHorizontalScale(); } public void setMetric(int n) { - focusMetric = focusMetric.values()[n]; - endSession(); + widgetSettings.setByIndex(FocusMetric.class, n); initBrainFlowMetric(); } public void setClassifier(int n) { - focusClassifier = focusClassifier.values()[n]; - endSession(); + widgetSettings.setByIndex(FocusClassifier.class, n); initBrainFlowMetric(); } + private void applyHorizontalScale() { + int windowValue = widgetSettings.get(FocusXLim.class).getValue(); + focusBar.adjustTimeAxis(windowValue); + } + public void setThreshold(int n) { - focusThreshold = focusThreshold.values()[n]; + widgetSettings.setByIndex(FocusThreshold.class, n); } public int getMetricExceedsThreshold() { @@ -394,19 +433,8 @@ class W_Focus extends Widget { //Called in DataProcessing.pde to update data even if widget is closed public void updateFocusWidgetData() { metricPrediction = updateFocusState(); - predictionExceedsThreshold = metricPrediction > focusThreshold.getValue(); - } - - public FocusMetric getFocusMetric() { - return focusMetric; - } - - public FocusThreshold getFocusThreshold() { - return focusThreshold; - } - - public FocusXLim getFocusWindow() { - return xLimit; + float focusThresholdValue = widgetSettings.get(FocusThreshold.class).getValue(); + predictionExceedsThreshold = metricPrediction > focusThresholdValue; } public void clear() { @@ -415,11 +443,11 @@ class W_Focus extends Widget { dataGrid.setString(df.format(metricPrediction), 0, 1); focusBar.update(metricPrediction); } -}; //end of class +}; //The following global functions are used by the Focus widget dropdowns. This method is the least amount of code. public void focusWindowDropdown(int n) { - ((W_Focus) widgetManager.getWidget("W_Focus")).setFocusHorizScale(n); + ((W_Focus) widgetManager.getWidget("W_Focus")).setFocusHorizontalScale(n); } public void focusMetricDropdown(int n) { From cc2103bd9e5e3ac60e22791a2d6a0b9ff00cf116 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Fri, 25 Apr 2025 12:04:43 -0500 Subject: [PATCH 26/29] Add FilterSettings to SessionSettings --- OpenBCI_GUI/FilterSettings.pde | 99 +++------------------------------ OpenBCI_GUI/FilterUI.pde | 74 ++---------------------- OpenBCI_GUI/OpenBCI_GUI.pde | 2 +- OpenBCI_GUI/SessionSettings.pde | 10 +++- 4 files changed, 21 insertions(+), 164 deletions(-) diff --git a/OpenBCI_GUI/FilterSettings.pde b/OpenBCI_GUI/FilterSettings.pde index fff293147..a627e325e 100644 --- a/OpenBCI_GUI/FilterSettings.pde +++ b/OpenBCI_GUI/FilterSettings.pde @@ -92,46 +92,19 @@ class FilterSettings { defaultValues = new FilterSettingsValues(channelCount); } - public boolean loadSettingsValues(String filename) { - try { - File file = new File(filename); - StringBuilder fileContents = new StringBuilder((int)file.length()); - Scanner scanner = new Scanner(file); - while(scanner.hasNextLine()) { - fileContents.append(scanner.nextLine() + System.lineSeparator()); - } - Gson gson = new Gson(); - values = gson.fromJson(fileContents.toString(), FilterSettingsValues.class); - return true; - } catch (IOException e) { - e.printStackTrace(); - File f = new File(filename); - if (f.exists()) { - if (f.delete()) { - println("FilterSettings: Could not load filter settings from disk. Deleting this file..."); - } else { - println("FilterSettings: Error deleting old/broken filter settings file! Please make sure the GUI has proper read/write permissions."); - } - } - return false; - } - } - public String getJson() { Gson gson = new GsonBuilder().setPrettyPrinting().create(); return gson.toJson(values); } - public boolean saveToFile(String filename) { - String json = getJson(); + public void loadSettingsFromJson(String json) { try { - FileWriter writer = new FileWriter(filename); - writer.write(json); - writer.close(); - return true; - } catch (IOException e) { - e.printStackTrace(); - return false; + Gson gson = new Gson(); + values = gson.fromJson(json, FilterSettingsValues.class); + filterSettingsWereLoadedFromFile = true; + } catch (Exception e) { + e.printStackTrace(); + println("FilterSettings: Could not load filter settings from JSON string."); } } @@ -143,62 +116,4 @@ class FilterSettings { public int getChannelCount() { return channelCount; } - - //Avoid error with popup being in another thread. - public void storeSettings() { - StringBuilder settingsFilename = new StringBuilder(directoryManager.getSettingsPath()); - settingsFilename.append("FilterSettings"); - settingsFilename.append("_"); - settingsFilename.append(getChannelCount()); - settingsFilename.append("Channels.json"); - String filename = settingsFilename.toString(); - File fileToSave = new File(filename); - FileChooser chooser = new FileChooser( - FileChooserMode.SAVE, - "storeFilterSettings", - fileToSave, - "Save filter settings to file"); - } - //Avoid error with popup being in another thread. - public void loadSettings() { - StringBuilder settingsFilename = new StringBuilder(directoryManager.getSettingsPath()); - settingsFilename.append("FilterSettings"); - settingsFilename.append("_"); - settingsFilename.append(getChannelCount()); - settingsFilename.append("Channels.json"); - String filename = settingsFilename.toString(); - File fileToLoad = new File(filename); - FileChooser chooser = new FileChooser( - FileChooserMode.LOAD, - "loadFilterSettings", - fileToLoad, - "Select settings file to load"); - } -} - -//Used by button in the Filter UI. Must be global and public. -public void loadFilterSettings(File selection) { - if (selection == null) { - output("Filters Settings file not selected."); - } else { - if (filterSettings.loadSettingsValues(selection.getAbsolutePath())) { - outputSuccess("Filter Settings Loaded!"); - filterSettingsWereLoadedFromFile = true; - } else { - outputError("Failed to load Filter Settings. The old/broken file has been deleted."); - } - } -} - -//Used by button in the Filter UI. Must be global and public. -public void storeFilterSettings(File selection) { - if (selection == null) { - output("Filter Settings file not selected."); - } else { - if (filterSettings.saveToFile(selection.getAbsolutePath())) { - outputSuccess("Filter Settings Saved!"); - } else { - outputError("Failed to save Filter Settings."); - } - } } \ No newline at end of file diff --git a/OpenBCI_GUI/FilterUI.pde b/OpenBCI_GUI/FilterUI.pde index ba3bae2dd..2e1c86b26 100644 --- a/OpenBCI_GUI/FilterUI.pde +++ b/OpenBCI_GUI/FilterUI.pde @@ -25,12 +25,9 @@ class FilterUIPopup extends PApplet implements Runnable { private final int HALF_OBJ_WIDTH = HEADER_OBJ_WIDTH/2; private final int NUM_HEADER_OBJECTS = 4; private final int NUM_COLUMNS = 5; - private final int NUM_FOOTER_OBJECTS = 3; private int[] headerObjX = new int[NUM_HEADER_OBJECTS]; private final int HEADER_OBJ_Y = SM_SPACER; private int[] columnObjX = new int[NUM_COLUMNS]; - private int footerObjY = 0; - private int[] footerObjX = new int[NUM_FOOTER_OBJECTS]; private String message = "Sample text string"; private String headerMessage = "Filters"; @@ -50,9 +47,6 @@ class FilterUIPopup extends PApplet implements Runnable { private ScrollableList bfGlobalFilterDropdown; private ScrollableList bfEnvironmentalNoiseDropdown; - private Button saveButton; - private Button loadButton; - private Button defaultButton; private Button masterOnOffButton; private Textfield masterFirstColumnTextfield; @@ -116,7 +110,7 @@ class FilterUIPopup extends PApplet implements Runnable { filterSettingsWereModifiedFadeCounter = new int[numChans]; fixedWidth = (HEADER_OBJ_WIDTH * 6) + SM_SPACER*5; - maxHeight = HEADER_HEIGHT*3 + SM_SPACER*(numChans+5) + uiObjectHeight*(numChans+2) + EXPANDER_HEIGHT; + maxHeight = HEADER_HEIGHT*3 + SM_SPACER*(numChans+5) + uiObjectHeight*(numChans+1) + EXPANDER_HEIGHT; shortHeight = HEADER_HEIGHT*2 + SM_SPACER*(1+5) + uiObjectHeight*(1+2) + LG_SPACER + EXPANDER_HEIGHT; variableHeight = maxHeight; //Include spacer on the outside left and right of all columns. Used to draw visual feedback @@ -307,11 +301,7 @@ class FilterUIPopup extends PApplet implements Runnable { } private void createAllCp5Objects() { - calculateXYForHeaderColumnsAndFooter(); - - createFilterSettingsSaveButton("saveFilterSettingsButton", "Save", footerObjX[0], footerObjY, HEADER_OBJ_WIDTH, uiObjectHeight); - createFilterSettingsLoadButton("loadFilterSettingsButton", "Load", footerObjX[1], footerObjY, HEADER_OBJ_WIDTH, uiObjectHeight); - createFilterSettingsDefaultButton("defaultFilterSettingsButton", "Reset", footerObjX[2], footerObjY, HEADER_OBJ_WIDTH, uiObjectHeight); + calculateXYForHeaderColumns(); createOnOffButtons(); createTextfields(); @@ -334,7 +324,7 @@ class FilterUIPopup extends PApplet implements Runnable { bfEnvironmentalNoiseDropdown.getCaptionLabel().setText(filterSettings.values.globalEnvFilter.getString()); } - private void calculateXYForHeaderColumnsAndFooter() { + private void calculateXYForHeaderColumns() { middle = width / 2; headerObjX[0] = middle - SM_SPACER*2 - HEADER_OBJ_WIDTH*2; @@ -348,17 +338,12 @@ class FilterUIPopup extends PApplet implements Runnable { columnObjX[3] = middle + HALF_OBJ_WIDTH + LG_SPACER; columnObjX[4] = middle + HALF_OBJ_WIDTH + LG_SPACER*2 + HEADER_OBJ_WIDTH; - footerObjX[0] = middle - HALF_OBJ_WIDTH - LG_SPACER - HEADER_OBJ_WIDTH; - footerObjX[1] = middle - HALF_OBJ_WIDTH; - footerObjX[2] = middle + HALF_OBJ_WIDTH + LG_SPACER; - setFooterObjYPosition(filterSettings.values.filterChannelSelect); - expanderLineOneEnd = middle - expanderBreakMiddle/2; expanderLineTwoStart = middle + expanderBreakMiddle/2; } public void arrangeAllObjectsXY() { - calculateXYForHeaderColumnsAndFooter(); + calculateXYForHeaderColumns(); bfGlobalFilterDropdown.setPosition(headerObjX[1], HEADER_OBJ_Y); bfEnvironmentalNoiseDropdown.setPosition(headerObjX[3], HEADER_OBJ_Y); @@ -400,10 +385,6 @@ class FilterUIPopup extends PApplet implements Runnable { filterTypeDropdowns[chan].setPosition(columnObjX[3], rowY); filterOrderDropdowns[chan].setPosition(filterOrderDropdownNewX, rowY); } - - saveButton.setPosition(footerObjX[0], footerObjY); - loadButton.setPosition(footerObjX[1], footerObjY); - defaultButton.setPosition(footerObjX[2], footerObjY); } // Master method to update objects from the FilterSettings Class @@ -920,37 +901,6 @@ class FilterUIPopup extends PApplet implements Runnable { cp5ElementsToCheck.add(masterFilterOrderDropdown); } - private void createFilterSettingsSaveButton(String name, String text, int _x, int _y, int _w, int _h) { - saveButton = createButton(cp5, name, text, _x, _y, _w, _h, h5, 12, colorNotPressed, OPENBCI_DARKBLUE); - saveButton.setBorderColor(OBJECT_BORDER_GREY); - saveButton.onClick(new CallbackListener() { - public void controlEvent(CallbackEvent theEvent) { - filterSettings.storeSettings(); - } - }); - } - - private void createFilterSettingsLoadButton(String name, String text, int _x, int _y, int _w, int _h) { - loadButton = createButton(cp5, name, text, _x, _y, _w, _h, h5, 12, colorNotPressed, OPENBCI_DARKBLUE); - loadButton.setBorderColor(OBJECT_BORDER_GREY); - loadButton.onClick(new CallbackListener() { - public void controlEvent(CallbackEvent theEvent) { - filterSettings.loadSettings(); - } - }); - } - - private void createFilterSettingsDefaultButton(String name, String text, int _x, int _y, int _w, int _h) { - defaultButton = createButton(cp5, name, text, _x, _y, _w, _h, h5, 12, colorNotPressed, OPENBCI_DARKBLUE); - defaultButton.setBorderColor(OBJECT_BORDER_GREY); - defaultButton.onClick(new CallbackListener() { - public void controlEvent(CallbackEvent theEvent) { - filterSettings.revertAllChannelsToDefaultValues(); - filterSettingsWereLoadedFromFile = true; - } - }); - } - private void createMasterOnOffButton(String name, final String text, int _x, int _y, int _w, int _h) { masterOnOffButton = createButton(cp5, name, text, _x, _y, _w, _h, 0, h2, 16, SUBNAV_LIGHTBLUE, WHITE, BUTTON_HOVER, BUTTON_PRESSED, (Integer) null, -2); masterOnOffButton.setCircularButton(true); @@ -1002,25 +952,9 @@ class FilterUIPopup extends PApplet implements Runnable { filterOrderDropdowns[chan].setVisible(showAllChannels); } - setFooterObjYPosition(myEnum); - saveButton.setPosition(footerObjX[0], footerObjY); - loadButton.setPosition(footerObjX[1], footerObjY); - needToResizePopup = true; } - private void setFooterObjYPosition(FilterChannelSelect myEnum) { - boolean showAllChannels = myEnum == FilterChannelSelect.CUSTOM_CHANNELS; - int numChans = filterSettings.getChannelCount(); - int footerMaxHeightY = HEADER_HEIGHT*2 + SM_SPACER*(numChans+4) + uiObjectHeight*(numChans+1) + LG_SPACER*2 + EXPANDER_HEIGHT; - int footerMinHeightY = HEADER_HEIGHT*2 + SM_SPACER*4 + uiObjectHeight + LG_SPACER + EXPANDER_HEIGHT; - footerObjY = showAllChannels ? footerMaxHeightY : footerMinHeightY; - - if (!EXPANDER_IS_USED) { - footerObjY -= EXPANDER_HEIGHT + SM_SPACER; - } - } - private void filterSettingWasModifiedOnChannel(int chan) { filterSettingsWereModified[chan] = true; filterSettingsWereModifiedFadeCounter[chan] = millis(); diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde index e0b4b28b9..b6cb0cbc1 100644 --- a/OpenBCI_GUI/OpenBCI_GUI.pde +++ b/OpenBCI_GUI/OpenBCI_GUI.pde @@ -298,8 +298,8 @@ SessionSettings sessionSettings; GuiSettings guiSettings; DataProcessing dataProcessing; FilterSettings filterSettings; -NetworkingUI networkUI; FilterUIPopup filterUI; +NetworkingUI networkUI; DeveloperCommandPopup developerCommandPopup; final int navBarHeight = 32; diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde index ade5acb22..40cfae2ae 100644 --- a/OpenBCI_GUI/SessionSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -43,7 +43,8 @@ class SessionSettings { KEY_LAYOUT = "widgetLayout", KEY_NETWORKING = "networking", KEY_CONTAINERS = "widgetContainerSettings", - KEY_WIDGET_SETTINGS = "widgetSettings"; + KEY_WIDGET_SETTINGS = "widgetSettings", + KEY_FILTER_SETTINGS = "filterSettings"; // File paths configuration private final String[][] SETTING_FILES = { @@ -99,6 +100,8 @@ class SessionSettings { saveSettingsJSONData.setJSONObject(KEY_CONTAINERS, saveWidgetContainerPositions()); saveSettingsJSONData.setJSONObject(KEY_WIDGET_SETTINGS, parseJSONObject(widgetManager.getWidgetSettingsAsJson())); + saveSettingsJSONData.setJSONObject(KEY_FILTER_SETTINGS, + parseJSONObject(filterSettings.getJson())); // Save to file saveJSONObject(saveSettingsJSONData, saveFilePath); @@ -141,6 +144,7 @@ class SessionSettings { applyNetworkingSettings(); applyWidgetLayout(); applyWidgetSettings(); + applyFilterSettings(); } /** @@ -222,6 +226,10 @@ class SessionSettings { loadSettingsJSONData.getJSONObject(KEY_WIDGET_SETTINGS).toString()); } + private void applyFilterSettings() { + filterSettings.loadSettingsFromJson(loadSettingsJSONData.getJSONObject(KEY_FILTER_SETTINGS).toString()); + } + /** * Get the appropriate settings file path based on mode and configuration */ From 5843fadcf038aaab7b148ff0e194cd8a1e767847 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Fri, 25 Apr 2025 12:55:49 -0500 Subject: [PATCH 27/29] Add EMGSettings to SessionSettings --- OpenBCI_GUI/EmgSettings.pde | 112 +++++--------------------------- OpenBCI_GUI/EmgSettingsUI.pde | 50 -------------- OpenBCI_GUI/SessionSettings.pde | 16 ++++- 3 files changed, 30 insertions(+), 148 deletions(-) diff --git a/OpenBCI_GUI/EmgSettings.pde b/OpenBCI_GUI/EmgSettings.pde index ada23f214..ffa778357 100644 --- a/OpenBCI_GUI/EmgSettings.pde +++ b/OpenBCI_GUI/EmgSettings.pde @@ -11,57 +11,35 @@ class EmgSettings { values = new EmgSettingsValues(); } - public boolean loadSettingsValues(String filename) { + public String getJson() { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + return gson.toJson(values); + } + + public boolean loadSettingsFromJson(String json) { try { - File file = new File(filename); - StringBuilder fileContents = new StringBuilder((int)file.length()); - Scanner scanner = new Scanner(file); - while(scanner.hasNextLine()) { - fileContents.append(scanner.nextLine() + System.lineSeparator()); - } Gson gson = new Gson(); - EmgSettingsValues tempValues = gson.fromJson(fileContents.toString(), EmgSettingsValues.class); + EmgSettingsValues tempValues = gson.fromJson(json, EmgSettingsValues.class); + + // Validate channel count matches if (tempValues.window.length != channelCount) { - outputError("Emg Settings: Loaded EMG Settings file has different number of channels than the current board."); + outputError("Emg Settings: Loaded EMG Settings JSON has different number of channels than the current board."); return false; } - //Explicitely copy values over to avoid reference issues - //(e.g. values = tempValues "nukes" the old values object) + + // Explicitly copy values to avoid reference issues values.window = tempValues.window; values.uvLimit = tempValues.uvLimit; values.creepIncreasing = tempValues.creepIncreasing; values.creepDecreasing = tempValues.creepDecreasing; values.minimumDeltaUV = tempValues.minimumDeltaUV; values.lowerThresholdMinimum = tempValues.lowerThresholdMinimum; + + settingsWereLoaded = true; return true; - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); - File f = new File(filename); - if (f.exists()) { - if (f.delete()) { - outputError("Emg Settings: Could not load EMG settings from disk. Deleting this file..."); - } else { - outputError("Emg Settings: Error deleting old/broken EMG settings file! Please make sure the GUI has proper read/write permissions."); - } - } - return false; - } - } - - public String getJson() { - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - return gson.toJson(values); - } - - public boolean saveToFile(String filename) { - String json = getJson(); - try { - FileWriter writer = new FileWriter(filename); - writer.write(json); - writer.close(); - return true; - } catch (IOException e) { - e.printStackTrace(); + outputError("EmgSettings: Could not load EMG settings from JSON string."); return false; } } @@ -76,39 +54,6 @@ class EmgSettings { return channelCount; } - //Avoid error with popup being in another thread. - public void storeSettings() { - StringBuilder settingsFilename = new StringBuilder(directoryManager.getSettingsPath()); - settingsFilename.append("EmgSettings"); - settingsFilename.append("_"); - settingsFilename.append(getChannelCount()); - settingsFilename.append("Channels.json"); - String filename = settingsFilename.toString(); - File fileToSave = new File(filename); - FileChooser chooser = new FileChooser( - FileChooserMode.SAVE, - "storeEmgSettings", - fileToSave, - "Save EMG settings to file"); - } - - //Avoid error with popup being in another thread. - public void loadSettings() { - StringBuilder settingsFilename = new StringBuilder(directoryManager.getSettingsPath()); - settingsFilename.append("EmgSettings"); - settingsFilename.append("_"); - settingsFilename.append(getChannelCount()); - settingsFilename.append("Channels.json"); - String filename = settingsFilename.toString(); - File fileToLoad = new File(filename); - FileChooser chooser = new FileChooser( - FileChooserMode.LOAD, - "loadEmgSettings", - fileToLoad, - "Select EMG settings file to load"); - - } - public boolean getSettingsWereLoaded() { return settingsWereLoaded; } @@ -116,29 +61,4 @@ class EmgSettings { public void setSettingsWereLoaded(boolean settingsWereLoaded) { this.settingsWereLoaded = settingsWereLoaded; } -} - -//Used by button in the EMG UI. Must be global and public. Called in above loadSettings method. -public void loadEmgSettings(File selection) { - if (selection == null) { - output("EMG Settings file not selected."); - } else { - if (dataProcessing.emgSettings.loadSettingsValues(selection.getAbsolutePath())) { - outputSuccess("EMG Settings Loaded!"); - dataProcessing.emgSettings.setSettingsWereLoaded(true); - } - } -} - -//Used by button in the EMG UI. Must be global and public. Called in above storeSettings method. -public void storeEmgSettings(File selection) { - if (selection == null) { - output("EMG Settings file not selected."); - } else { - if (dataProcessing.emgSettings.saveToFile(selection.getAbsolutePath())) { - outputSuccess("EMG Settings Saved!"); - } else { - outputError("Failed to save EMG Settings."); - } - } } \ No newline at end of file diff --git a/OpenBCI_GUI/EmgSettingsUI.pde b/OpenBCI_GUI/EmgSettingsUI.pde index 7e3991faa..a949ba773 100644 --- a/OpenBCI_GUI/EmgSettingsUI.pde +++ b/OpenBCI_GUI/EmgSettingsUI.pde @@ -27,11 +27,6 @@ class EmgSettingsUI extends PApplet implements Runnable { private boolean isFixedHeight; private int fixedHeight; private int[] dropdownYPositions; - private final int NUM_FOOTER_OBJECTS = 3; - private final int FOOTER_OBJECT_WIDTH = 45; - private final int FOOTER_OBJECT_HEIGHT = 26; - private int footerObjY; - private int[] footerObjX = new int[NUM_FOOTER_OBJECTS]; private final color HEADER_COLOR = OPENBCI_BLUE; private final color BACKGROUND_COLOR = GREY_235; @@ -61,10 +56,6 @@ class EmgSettingsUI extends PApplet implements Runnable { private String[] channelLabels; - private Button saveButton; - private Button loadButton; - private Button defaultButton; - @Override public void run() { PApplet.runSketch(new String[] {HEADER_MESSAGE}, this); @@ -235,17 +226,6 @@ class EmgSettingsUI extends PApplet implements Runnable { } private void createAllUIObjects() { - final int HALF_FOOTER_HEIGHT = (FOOTER_PADDING + (DROPDOWN_SPACER * 2)) / 2; - footerObjY = y + h - HALF_FOOTER_HEIGHT - (FOOTER_OBJECT_HEIGHT / 2); - int middle = x + w / 2; - int halfObjWidth = FOOTER_OBJECT_WIDTH / 2; - footerObjX[0] = middle - halfObjWidth - PADDING_12 - FOOTER_OBJECT_WIDTH; - footerObjX[1] = middle - halfObjWidth; - footerObjX[2] = middle + halfObjWidth + PADDING_12; - createEmgSettingsSaveButton("saveEmgSettingsButton", "Save", footerObjX[0], footerObjY, FOOTER_OBJECT_WIDTH, FOOTER_OBJECT_HEIGHT); - createEmgSettingsLoadButton("loadEmgSettingsButton", "Load", footerObjX[1], footerObjY, FOOTER_OBJECT_WIDTH, FOOTER_OBJECT_HEIGHT); - createEmgSettingsDefaultButton("defaultEmgSettingsButton", "Reset", footerObjX[2], footerObjY, FOOTER_OBJECT_WIDTH, FOOTER_OBJECT_HEIGHT); - channelLabels = new String[channelCount]; for (int i = 0; i < channelCount; i++) { channelLabels[i] = "Channel " + (i+1); @@ -369,36 +349,6 @@ class EmgSettingsUI extends PApplet implements Runnable { } } - private void createEmgSettingsSaveButton(String name, String text, int _x, int _y, int _w, int _h) { - saveButton = createButton(emgCp5, name, text, _x, _y, _w, _h, h5, 12, colorNotPressed, OPENBCI_DARKBLUE); - saveButton.setBorderColor(OBJECT_BORDER_GREY); - saveButton.onClick(new CallbackListener() { - public void controlEvent(CallbackEvent theEvent) { - dataProcessing.emgSettings.storeSettings(); - } - }); - } - - private void createEmgSettingsLoadButton(String name, String text, int _x, int _y, int _w, int _h) { - loadButton = createButton(emgCp5, name, text, _x, _y, _w, _h, h5, 12, colorNotPressed, OPENBCI_DARKBLUE); - loadButton.setBorderColor(OBJECT_BORDER_GREY); - loadButton.onClick(new CallbackListener() { - public void controlEvent(CallbackEvent theEvent) { - dataProcessing.emgSettings.loadSettings(); - } - }); - } - - private void createEmgSettingsDefaultButton(String name, String text, int _x, int _y, int _w, int _h) { - defaultButton = createButton(emgCp5, name, text, _x, _y, _w, _h, h5, 12, colorNotPressed, OPENBCI_DARKBLUE); - defaultButton.setBorderColor(OBJECT_BORDER_GREY); - defaultButton.onClick(new CallbackListener() { - public void controlEvent(CallbackEvent theEvent) { - dataProcessing.emgSettings.revertAllChannelsToDefaultValues(); - } - }); - } - private void updateCp5Objects() { for (int i = 0; i < channelCount; i++) { //Fetch values from the EmgSettingsValues object diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde index 40cfae2ae..f8d1bf644 100644 --- a/OpenBCI_GUI/SessionSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -44,7 +44,8 @@ class SessionSettings { KEY_NETWORKING = "networking", KEY_CONTAINERS = "widgetContainerSettings", KEY_WIDGET_SETTINGS = "widgetSettings", - KEY_FILTER_SETTINGS = "filterSettings"; + KEY_FILTER_SETTINGS = "filterSettings", + KEY_EMG_SETTINGS = "emgSettings"; // File paths configuration private final String[][] SETTING_FILES = { @@ -102,6 +103,8 @@ class SessionSettings { parseJSONObject(widgetManager.getWidgetSettingsAsJson())); saveSettingsJSONData.setJSONObject(KEY_FILTER_SETTINGS, parseJSONObject(filterSettings.getJson())); + saveSettingsJSONData.setJSONObject(KEY_EMG_SETTINGS, + parseJSONObject(dataProcessing.emgSettings.getJson())); // Save to file saveJSONObject(saveSettingsJSONData, saveFilePath); @@ -145,6 +148,7 @@ class SessionSettings { applyWidgetLayout(); applyWidgetSettings(); applyFilterSettings(); + applyEmgSettings(); } /** @@ -227,7 +231,15 @@ class SessionSettings { } private void applyFilterSettings() { - filterSettings.loadSettingsFromJson(loadSettingsJSONData.getJSONObject(KEY_FILTER_SETTINGS).toString()); + JSONObject filterSettingsJSON = loadSettingsJSONData.getJSONObject(KEY_FILTER_SETTINGS); + String filterSettingsString = filterSettingsJSON.toString(); + filterSettings.loadSettingsFromJson(filterSettingsString); + } + + private void applyEmgSettings() { + JSONObject emgSettingsJSON = loadSettingsJSONData.getJSONObject(KEY_EMG_SETTINGS); + String emgSettingsString = emgSettingsJSON.toString(); + dataProcessing.emgSettings.loadSettingsFromJson(emgSettingsString); } /** From c002eaf08ffa347b96f5391a84a61452fef20bfc Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Mon, 28 Apr 2025 12:24:24 -0500 Subject: [PATCH 28/29] Fix null pointer in ADS1299SettingsController and fix front end bug --- OpenBCI_GUI/ADS1299SettingsController.pde | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/OpenBCI_GUI/ADS1299SettingsController.pde b/OpenBCI_GUI/ADS1299SettingsController.pde index 0a9de3ef8..6ee38e984 100644 --- a/OpenBCI_GUI/ADS1299SettingsController.pde +++ b/OpenBCI_GUI/ADS1299SettingsController.pde @@ -2,7 +2,7 @@ import org.apache.commons.lang3.tuple.Pair; class ADS1299SettingsController { - private PApplet _parentApplet; + private PApplet parentApplet; private boolean isVisible = false; protected int x, y, w, h; protected final int PADDING_3 = 3; @@ -71,9 +71,9 @@ class ADS1299SettingsController { h = _h; channelBarHeight = _channelBarHeight; - _parentApplet = _parentApplet; - hwsCp5 = new ControlP5(_parentApplet); - hwsCp5.setGraphics(_parentApplet, 0,0); + this.parentApplet = _parentApplet; + hwsCp5 = new ControlP5(parentApplet); + hwsCp5.setGraphics(parentApplet, 0,0); hwsCp5.setAutoDraw(false); int colOffset = (w / CONTROL_BUTTON_COUNT) / 2; @@ -180,7 +180,7 @@ class ADS1299SettingsController { toggleWidthAndHeight = DEFAULT_TOGGLE_WIDTH; } - hwsCp5.setGraphics(_parentApplet, 0, 0); + hwsCp5.setGraphics(parentApplet, 0, 0); int colOffset = (w / CONTROL_BUTTON_COUNT) / 2; int button_y = y + h + PADDING_3; @@ -233,7 +233,7 @@ class ADS1299SettingsController { dropdownY = int(y + (channelBarHeight * rowCount) + ((channelBarHeight - dropdownH) / 2)); final int buttonXIncrement = spaceBetweenButtons + dropdownW; - int toggleX = dropdownX + (dropdownW / 2) - (toggleWidthAndHeight); + int toggleX = dropdownX + (dropdownW / 2) - (toggleWidthAndHeight / 2); channelSelectToggles[i].setPosition(toggleX, dropdownY); channelSelectToggles[i].setSize(toggleWidthAndHeight, toggleWidthAndHeight); From 916b941722378f01e191fb0a9cc87a2e96127220 Mon Sep 17 00:00:00 2001 From: Richard Waltman Date: Tue, 29 Apr 2025 20:39:54 -0500 Subject: [PATCH 29/29] Add the option to switch band power widget to linear scale #1139 --- OpenBCI_GUI/BandPowerEnums.pde | 72 ++++--------- OpenBCI_GUI/FFTEnums.pde | 8 +- OpenBCI_GUI/W_BandPower.pde | 179 ++++++++++++++------------------- OpenBCI_GUI/W_FFT.pde | 16 +-- OpenBCI_GUI/W_Spectrogram.pde | 18 ++-- 5 files changed, 114 insertions(+), 179 deletions(-) diff --git a/OpenBCI_GUI/BandPowerEnums.pde b/OpenBCI_GUI/BandPowerEnums.pde index 1f47dc6e8..c0597ffb1 100644 --- a/OpenBCI_GUI/BandPowerEnums.pde +++ b/OpenBCI_GUI/BandPowerEnums.pde @@ -1,13 +1,12 @@ -public enum BPAutoClean implements IndexingInterface -{ - ON (0, "On"), - OFF (1, "Off"); +public enum BPLogLin implements IndexingInterface { + LOG (0, "Log"), + LINEAR (1, "Linear"); private int index; private String label; - BPAutoClean(int _index, String _label) { + BPLogLin(int _index, String _label) { this.index = _index; this.label = _label; } @@ -23,62 +22,25 @@ public enum BPAutoClean implements IndexingInterface } } -public enum BPAutoCleanThreshold implements IndexingInterface -{ - FORTY (0, 40f, "40 uV"), - FIFTY (1, 50f, "50 uV"), - SIXTY (2, 60f, "60 uV"), - SEVENTY (3, 70f, "70 uV"), - EIGHTY (4, 80f, "80 uV"), - NINETY (5, 90f, "90 uV"), - ONE_HUNDRED(6, 100f, "100 uV"); +public enum BPVerticalScale implements IndexingInterface { + SCALE_10 (0, 10, "10 uV"), + SCALE_50 (1, 50, "50 uV"), + SCALE_100 (2, 100, "100 uV"), + SCALE_500 (3, 500, "500 uV"), + SCALE_1000 (4, 1000, "1000 uV"), + SCALE_1500 (5, 1500, "1500 uV"); private int index; - private float value; + private final int value; private String label; - BPAutoCleanThreshold(int _index, float _value, String _label) { - this.index = _index; - this.value = _value; - this.label = _label; - } - - public float getValue() { - return value; - } - - @Override - public String getString() { - return label; - } - - @Override - public int getIndex() { - return index; - } -} - -public enum BPAutoCleanTimer implements IndexingInterface -{ - HALF_SECOND (0, 500, ".5 sec"), - ONE_SECOND (1, 1000, "1 sec"), - THREE_SECONDS (2, 2000, "3 sec"), - FIVE_SECONDS (3, 5000, "5 sec"), - TEN_SECONDS (4, 10000, "10 sec"), - TWENTY_SECONDS (5, 20000, "20 sec"), - THIRTY_SECONDS(6, 30000, "30 sec"); - - private int index; - private float value; - private String label; - - BPAutoCleanTimer(int _index, float _value, String _label) { - this.index = _index; - this.value = _value; - this.label = _label; + BPVerticalScale(int index, int value, String label) { + this.index = index; + this.value = value; + this.label = label; } - public float getValue() { + public int getValue() { return value; } diff --git a/OpenBCI_GUI/FFTEnums.pde b/OpenBCI_GUI/FFTEnums.pde index b351a543d..ce1b1e775 100644 --- a/OpenBCI_GUI/FFTEnums.pde +++ b/OpenBCI_GUI/FFTEnums.pde @@ -132,7 +132,9 @@ public enum FFTVerticalScale implements IndexingInterface { SCALE_10 (0, 10, "10 uV"), SCALE_50 (1, 50, "50 uV"), SCALE_100 (2, 100, "100 uV"), - SCALE_1000 (3, 1000, "1000 uV"); + SCALE_500 (3, 500, "500 uV"), + SCALE_1000 (3, 1000, "1000 uV"), + SCALE_1500 (4, 1500, "1500 uV"); private int index; private final int value; @@ -159,14 +161,14 @@ public enum FFTVerticalScale implements IndexingInterface { } } -public enum FFTLogLin implements IndexingInterface { +public enum GraphLogLin implements IndexingInterface { LOG (0, "Log"), LIN (1, "Linear"); private int index; private String label; - FFTLogLin(int index, String label) { + GraphLogLin(int index, String label) { this.index = index; this.label = label; } diff --git a/OpenBCI_GUI/W_BandPower.pde b/OpenBCI_GUI/W_BandPower.pde index 8040e0f9e..7043a04f6 100644 --- a/OpenBCI_GUI/W_BandPower.pde +++ b/OpenBCI_GUI/W_BandPower.pde @@ -10,12 +10,11 @@ // // // Created by: Wangshu Sun, May 2017 // // Modified by: Richard Waltman, March 2022 // -// Refactored by: Richard Waltman, March 2025 // +// Refactored by: Richard Waltman, April 2025 // // // //////////////////////////////////////////////////////////////////////////////////////////////////////// class W_BandPower extends WidgetWithSettings { - // indexes private final int DELTA = 0; // 1-4 Hz private final int THETA = 1; // 4-8 Hz private final int ALPHA = 2; // 8-13 Hz @@ -32,24 +31,61 @@ class W_BandPower extends WidgetWithSettings { private List cp5ElementsToCheck; - int[] autoCleanTimers; - boolean[] previousThresholdCrossed; - W_BandPower() { super(); widgetTitle = "Band Power"; - autoCleanTimers = new int[currentBoard.getNumEXGChannels()]; - previousThresholdCrossed = new boolean[currentBoard.getNumEXGChannels()]; + createPlot(); + } + + @Override + protected void initWidgetSettings() { + super.initWidgetSettings(); + widgetSettings.set(BPVerticalScale.class, BPVerticalScale.SCALE_100) + .set(GraphLogLin.class, GraphLogLin.LOG) + .set(FFTSmoothingFactor.class, globalFFTSettings.getSmoothingFactor()) + .set(FFTFilteredEnum.class, globalFFTSettings.getFilteredEnum()); + + initDropdown(BPVerticalScale.class, "bandPowerVerticalScaleDropdown", "Max uV"); + initDropdown(GraphLogLin.class, "bandPowerLogLinDropdown", "Log/Lin"); + initDropdown(FFTSmoothingFactor.class, "bandPowerSmoothingDropdown", "Smooth"); + initDropdown(FFTFilteredEnum.class, "bandPowerDataFilteringDropdown", "Filters"); + bpChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); + bpChanSelect.activateAllButtons(); + cp5ElementsToCheck = new ArrayList(); + cp5ElementsToCheck.addAll(bpChanSelect.getCp5ElementsForOverlapCheck()); + saveActiveChannels(bpChanSelect.getActiveChannels()); + widgetSettings.saveDefaults(); + } + + @Override + protected void applySettings() { + updateDropdownLabel(BPVerticalScale.class, "bandPowerVerticalScaleDropdown"); + updateDropdownLabel(GraphLogLin.class, "bandPowerLogLinDropdown"); + updateDropdownLabel(FFTSmoothingFactor.class, "bandPowerSmoothingDropdown"); + updateDropdownLabel(FFTFilteredEnum.class, "bandPowerDataFilteringDropdown"); + applyActiveChannels(bpChanSelect); + applyVerticalScale(); + applyPlotLogScale(); + } + + @Override + protected void updateChannelSettings() { + if (bpChanSelect != null) { + saveActiveChannels(bpChanSelect.getActiveChannels()); + } + } + + private void createPlot() { // Setup for the BandPower plot bp_plot = new GPlot(ourApplet, x, y-NAV_HEIGHT, w, h+NAV_HEIGHT); // bp_plot.setPos(x, y+NAV_HEIGHT); bp_plot.setDim(w, h); bp_plot.setLogScale("y"); - bp_plot.setYLim(0.1, 100); + bp_plot.setYLim(0.1, 100); // Lower limit must be > 0 for log scale bp_plot.setXLim(0, 5); - bp_plot.getYAxis().setNTicks(9); + bp_plot.getYAxis().setNTicks(4); bp_plot.getXAxis().setNTicks(0); bp_plot.getTitle().setTextAlignment(LEFT); bp_plot.getTitle().setRelativePos(0); @@ -82,53 +118,11 @@ class W_BandPower extends WidgetWithSettings { ); //setting color of text label for each histogram bar on the x axis bp_plot.getHistogram().setFontColor(OPENBCI_DARKBLUE); - } - - @Override - protected void initWidgetSettings() { - super.initWidgetSettings(); - widgetSettings.set(BPAutoClean.class, BPAutoClean.OFF) - .set(BPAutoCleanThreshold.class, BPAutoCleanThreshold.FIFTY) - .set(BPAutoCleanTimer.class, BPAutoCleanTimer.THREE_SECONDS) - .set(FFTSmoothingFactor.class, globalFFTSettings.getSmoothingFactor()) - .set(FFTFilteredEnum.class, globalFFTSettings.getFilteredEnum()); - - initDropdown(BPAutoClean.class, "bandPowerAutoCleanDropdown", "Auto Clean"); - initDropdown(BPAutoCleanThreshold.class, "bandPowerAutoCleanThresholdDropdown", "Threshold"); - initDropdown(BPAutoCleanTimer.class, "bandPowerAutoCleanTimerDropdown", "Timer"); - initDropdown(FFTSmoothingFactor.class, "bandPowerSmoothingDropdown", "Smooth"); - initDropdown(FFTFilteredEnum.class, "bandPowerDataFilteringDropdown", "Filtered?"); - - bpChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); - bpChanSelect.activateAllButtons(); - cp5ElementsToCheck = new ArrayList(); - cp5ElementsToCheck.addAll(bpChanSelect.getCp5ElementsForOverlapCheck()); - saveActiveChannels(bpChanSelect.getActiveChannels()); - widgetSettings.saveDefaults(); - } - - @Override - protected void applySettings() { - updateDropdownLabel(BPAutoClean.class, "bandPowerAutoCleanDropdown"); - updateDropdownLabel(BPAutoCleanThreshold.class, "bandPowerAutoCleanThresholdDropdown"); - updateDropdownLabel(BPAutoCleanTimer.class, "bandPowerAutoCleanTimerDropdown"); - updateDropdownLabel(FFTSmoothingFactor.class, "bandPowerSmoothingDropdown"); - updateDropdownLabel(FFTFilteredEnum.class, "bandPowerDataFilteringDropdown"); - applyActiveChannels(bpChanSelect); - } - - @Override - protected void updateChannelSettings() { - if (bpChanSelect != null) { - saveActiveChannels(bpChanSelect.getActiveChannels()); - } + applyPlotLogScale(); } public void update() { super.update(); - - // If enabled, automatically turn channels on or off in ExGChannelSelect for this widget - autoCleanByEnableDisableChannels(); //Update channel checkboxes and active channels bpChanSelect.update(x, y, w); @@ -202,50 +196,6 @@ class W_BandPower extends WidgetWithSettings { return normalizedBandPowers; } - private void autoCleanByEnableDisableChannels() { - BPAutoClean autoClean = widgetSettings.get(BPAutoClean.class); - BPAutoCleanThreshold autoCleanThreshold = widgetSettings.get(BPAutoCleanThreshold.class); - BPAutoCleanTimer autoCleanTimer = widgetSettings.get(BPAutoCleanTimer.class); - if (autoClean == BPAutoClean.OFF) { - return; - } - - int numChannels = currentBoard.getNumEXGChannels(); - for (int i = 0; i < numChannels; i++) { - float uvrms = dataProcessing.data_std_uV[i]; - boolean thresholdCrossed = uvrms > autoCleanThreshold.getValue(); - - int currentMillis = millis(); - - //Check for state change. Reset timer on either state. - if (thresholdCrossed != previousThresholdCrossed[i]) { - previousThresholdCrossed[i] = thresholdCrossed; - autoCleanTimers[i] = currentMillis; - } - - //Auto-disable a channel if it's above the threshold and has been for the timer duration - boolean timerDurationExceeded = currentMillis - autoCleanTimers[i] > autoCleanTimer.getValue(); - if (timerDurationExceeded) { - boolean enableChannel = !thresholdCrossed; - bpChanSelect.setToggleState(i, enableChannel); - } - } - } - - public void setAutoClean(int n) { - widgetSettings.setByIndex(BPAutoClean.class, n); - Arrays.fill(previousThresholdCrossed, false); - Arrays.fill(autoCleanTimers, 0); - } - - public void setAutoCleanThreshold(int n) { - widgetSettings.setByIndex(BPAutoCleanThreshold.class, n); - } - - public void setAutoCleanTimer(int n) { - widgetSettings.setByIndex(BPAutoCleanTimer.class, n); - } - //Called in DataProcessing.pde to update data even if widget is closed public void updateBandPowerWidgetData() { float normalizingSum = 0; @@ -265,6 +215,31 @@ class W_BandPower extends WidgetWithSettings { } } + public void setVerticalScale(int n) { + widgetSettings.setByIndex(BPVerticalScale.class, n); + applyVerticalScale(); + } + + public void setLogLin(int n) { + widgetSettings.setByIndex(GraphLogLin.class, n); + applyPlotLogScale(); + } + + private void applyVerticalScale() { + BPVerticalScale scale = widgetSettings.get(BPVerticalScale.class); + int scaleValue = scale.getValue(); + bp_plot.setYLim(0.1, scaleValue); // Lower limit must be > 0 for log scale + } + + private void applyPlotLogScale() { + GraphLogLin logLin = widgetSettings.get(GraphLogLin.class); + if (logLin == GraphLogLin.LOG) { + bp_plot.setLogScale("y"); + } else { + bp_plot.setLogScale(""); + } + } + public void setSmoothingDropdownFrontend(FFTSmoothingFactor _smoothingFactor) { widgetSettings.set(FFTSmoothingFactor.class, _smoothingFactor); updateDropdownLabel(FFTSmoothingFactor.class, "bandPowerSmoothingDropdown"); @@ -276,16 +251,12 @@ class W_BandPower extends WidgetWithSettings { } }; -public void bandPowerAutoCleanDropdown(int n) { - ((W_BandPower) widgetManager.getWidget("W_BandPower")).setAutoClean(n); -} - -public void bandPowerAutoCleanThresholdDropdown(int n) { - ((W_BandPower) widgetManager.getWidget("W_BandPower")).setAutoCleanThreshold(n); +public void bandPowerVerticalScaleDropdown(int n) { + ((W_BandPower) widgetManager.getWidget("W_BandPower")).setVerticalScale(n); } -public void bandPowerAutoCleanTimerDropdown(int n) { - ((W_BandPower) widgetManager.getWidget("W_BandPower")).setAutoCleanTimer(n); +public void bandPowerLogLinDropdown(int n) { + ((W_BandPower) widgetManager.getWidget("W_BandPower")).setLogLin(n); } public void bandPowerSmoothingDropdown(int n) { diff --git a/OpenBCI_GUI/W_FFT.pde b/OpenBCI_GUI/W_FFT.pde index 846b445b9..fdf7ba292 100644 --- a/OpenBCI_GUI/W_FFT.pde +++ b/OpenBCI_GUI/W_FFT.pde @@ -36,15 +36,15 @@ class W_Fft extends WidgetWithSettings { super.initWidgetSettings(); widgetSettings.set(FFTMaxFrequency.class, FFTMaxFrequency.MAX_60) .set(FFTVerticalScale.class, FFTVerticalScale.SCALE_100) - .set(FFTLogLin.class, FFTLogLin.LOG) + .set(GraphLogLin.class, GraphLogLin.LOG) .set(FFTSmoothingFactor.class, globalFFTSettings.getSmoothingFactor()) .set(FFTFilteredEnum.class, globalFFTSettings.getFilteredEnum()); initDropdown(FFTMaxFrequency.class, "fftMaxFrequencyDropdown", "Max Hz"); initDropdown(FFTVerticalScale.class, "fftVerticalScaleDropdown", "Max uV"); - initDropdown(FFTLogLin.class, "fftLogLinDropdown", "Log/Lin"); + initDropdown(GraphLogLin.class, "GraphLogLinDropdown", "Log/Lin"); initDropdown(FFTSmoothingFactor.class, "fftSmoothingDropdown", "Smooth"); - initDropdown(FFTFilteredEnum.class, "fftFilteringDropdown", "Filters?"); + initDropdown(FFTFilteredEnum.class, "fftFilteringDropdown", "Filters"); fftChanSelect = new ExGChannelSelect(ourApplet, x, y, w, navH); fftChanSelect.activateAllButtons(); @@ -61,7 +61,7 @@ class W_Fft extends WidgetWithSettings { protected void applySettings() { updateDropdownLabel(FFTMaxFrequency.class, "fftMaxFrequencyDropdown"); updateDropdownLabel(FFTVerticalScale.class, "fftVerticalScaleDropdown"); - updateDropdownLabel(FFTLogLin.class, "fftLogLinDropdown"); + updateDropdownLabel(GraphLogLin.class, "GraphLogLinDropdown"); updateDropdownLabel(FFTSmoothingFactor.class, "fftSmoothingDropdown"); updateDropdownLabel(FFTFilteredEnum.class, "fftFilteringDropdown"); applyActiveChannels(fftChanSelect); @@ -233,13 +233,13 @@ class W_Fft extends WidgetWithSettings { } public void setLogLin(int n) { - widgetSettings.setByIndex(FFTLogLin.class, n); + widgetSettings.setByIndex(GraphLogLin.class, n); setPlotLogScale(); } private void setPlotLogScale() { - FFTLogLin logLin = widgetSettings.get(FFTLogLin.class); - if (logLin == FFTLogLin.LOG) { + GraphLogLin logLin = widgetSettings.get(GraphLogLin.class); + if (logLin == GraphLogLin.LOG) { fftPlot.setLogScale("y"); } else { fftPlot.setLogScale(""); @@ -266,7 +266,7 @@ public void fftVerticalScaleDropdown(int n) { ((W_Fft) widgetManager.getWidget("W_Fft")).setVerticalScale(n); } -public void fftLogLinDropdown(int n) { +public void GraphLogLinDropdown(int n) { ((W_Fft) widgetManager.getWidget("W_Fft")).setLogLin(n); } diff --git a/OpenBCI_GUI/W_Spectrogram.pde b/OpenBCI_GUI/W_Spectrogram.pde index c3624575e..83ab17302 100644 --- a/OpenBCI_GUI/W_Spectrogram.pde +++ b/OpenBCI_GUI/W_Spectrogram.pde @@ -70,11 +70,11 @@ class W_Spectrogram extends WidgetWithSettings { super.initWidgetSettings(); widgetSettings.set(SpectrogramMaxFrequency.class, SpectrogramMaxFrequency.MAX_60) .set(SpectrogramWindowSize.class, SpectrogramWindowSize.ONE_MINUTE) - .set(FFTLogLin.class, FFTLogLin.LIN); + .set(GraphLogLin.class, GraphLogLin.LIN); initDropdown(SpectrogramMaxFrequency.class, "spectrogramMaxFrequencyDropdown", "Max Hz"); initDropdown(SpectrogramWindowSize.class, "spectrogramWindowDropdown", "Window"); - initDropdown(FFTLogLin.class, "spectrogramLogLinDropdown", "Log/Lin"); + initDropdown(GraphLogLin.class, "spectrogramLogLinDropdown", "Log/Lin"); spectChanSelectTop = new DualExGChannelSelect(ourApplet, x, y, w, navH, true); spectChanSelectBot = new DualExGChannelSelect(ourApplet, x, y + navH, w, navH, false); @@ -90,7 +90,7 @@ class W_Spectrogram extends WidgetWithSettings { protected void applySettings() { updateDropdownLabel(SpectrogramMaxFrequency.class, "spectrogramMaxFrequencyDropdown"); updateDropdownLabel(SpectrogramWindowSize.class, "spectrogramWindowDropdown"); - updateDropdownLabel(FFTLogLin.class, "spectrogramLogLinDropdown"); + updateDropdownLabel(GraphLogLin.class, "spectrogramLogLinDropdown"); applyMaxFrequency(); applyWindowSize(); applyChannelSettings(); @@ -205,7 +205,7 @@ class W_Spectrogram extends WidgetWithSettings { } private void drawSpectrogramPoints() { - FFTLogLin logLin = widgetSettings.get(FFTLogLin.class); + GraphLogLin logLin = widgetSettings.get(GraphLogLin.class); for (int i = 0; i <= dataImg.height/2; i++) { // Draw top spectrogram (left channels) @@ -217,10 +217,10 @@ class W_Spectrogram extends WidgetWithSettings { } } - private void drawSpectrogramPoint(List channels, int freqBand, int yPosition, FFTLogLin logLin) { + private void drawSpectrogramPoint(List channels, int freqBand, int yPosition, GraphLogLin logLin) { float hueValue = hueLimit - map((fftAvgs(channels, freqBand)*32), 0, 256, 0, hueLimit); - if (logLin == FFTLogLin.LOG) { + if (logLin == GraphLogLin.LOG) { hueValue = map(log10(hueValue), 0, 2, 0, hueLimit); } @@ -374,12 +374,12 @@ class W_Spectrogram extends WidgetWithSettings { return; } } - FFTLogLin logLin = widgetSettings.get(FFTLogLin.class); + GraphLogLin logLin = widgetSettings.get(GraphLogLin.class); pushStyle(); //draw color scale reference to the right of the spectrogram for (int i = 0; i < colorScaleHeight; i++) { float hueValue = hueLimit - map(i * 2, 0, colorScaleHeight*2, 0, hueLimit); - if (logLin == FFTLogLin.LOG) { + if (logLin == GraphLogLin.LOG) { hueValue = map(log(hueValue) / log(10), 0, 2, 0, hueLimit); } //println(hueValue); @@ -481,7 +481,7 @@ class W_Spectrogram extends WidgetWithSettings { } public void setLogLin(int n) { - widgetSettings.setByIndex(FFTLogLin.class, n); + widgetSettings.setByIndex(GraphLogLin.class, n); } public void setMaxFrequency(int n) {