diff --git a/.travis.yml b/.travis.yml index b3444f4f2..928f9e940 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ jobs: branches: only: - master - - development + #- development before_install: - if [ "$TRAVIS_OS_NAME" = osx ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then openssl aes-256-cbc -K $encrypted_2f5d2771e3cb_key -iv $encrypted_2f5d2771e3cb_iv -in release_script/mac_only/Certificates.p12.enc -out release_script/mac_only/Certificates.p12 -d; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 345ef7351..4883d17e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# v5.2.1 + +### Improvements + +- Add ability to map channels to EMG Joystick Inputs #1156 +- Fix alignment of UI objects in popup windows and EMG settings UI #1157 +- Rename "smoothing" to "window" in EMG settings UI #1158 +- Add EMG Joystick settings to Session Settings #1159 + # v5.2.0 ### Bug Fixes diff --git a/OpenBCI_GUI/EmgSettings.pde b/OpenBCI_GUI/EmgSettings.pde index 6661b6c4a..e4947932f 100644 --- a/OpenBCI_GUI/EmgSettings.pde +++ b/OpenBCI_GUI/EmgSettings.pde @@ -21,13 +21,13 @@ class EmgSettings { } Gson gson = new Gson(); EmgSettingsValues tempValues = gson.fromJson(fileContents.toString(), EmgSettingsValues.class); - if (tempValues.smoothing.length != channelCount) { + if (tempValues.window.length != channelCount) { outputError("Emg Settings: Loaded EMG Settings file 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) - values.smoothing = tempValues.smoothing; + values.window = tempValues.window; values.uvLimit = tempValues.uvLimit; values.creepIncreasing = tempValues.creepIncreasing; values.creepDecreasing = tempValues.creepDecreasing; diff --git a/OpenBCI_GUI/EmgSettingsEnums.pde b/OpenBCI_GUI/EmgSettingsEnums.pde index 819802fc4..c7d195693 100644 --- a/OpenBCI_GUI/EmgSettingsEnums.pde +++ b/OpenBCI_GUI/EmgSettingsEnums.pde @@ -3,7 +3,7 @@ interface EmgSettingsEnum { public String getString(); } -public enum EmgSmoothing implements EmgSettingsEnum +public enum EmgWindow implements EmgSettingsEnum { ONE_HUNDREDTH_SECOND (0, "0.01 s", .01f), ONE_TENTH_SECOND (1, "0.1 s", .1f), @@ -18,7 +18,7 @@ public enum EmgSmoothing implements EmgSettingsEnum private String name; private float value; - EmgSmoothing(int index, String name, float value) { + EmgWindow(int index, String name, float value) { this.index = index; this.name = name; this.value = value; diff --git a/OpenBCI_GUI/EmgSettingsUI.pde b/OpenBCI_GUI/EmgSettingsUI.pde index 5577adec0..7573afc4d 100644 --- a/OpenBCI_GUI/EmgSettingsUI.pde +++ b/OpenBCI_GUI/EmgSettingsUI.pde @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////// -// EmgSettingsUI.pde // +// EmgSettingsUI.pde // // Display the Emg Settings UI as a popup // +// Note: This window is never resized. // // // //////////////////////////////////////////////////////////// @@ -14,7 +15,7 @@ class EmgSettingsUI extends PApplet implements Runnable { private ControlP5 emgCp5; private int x, y, w, h; private final int HEADER_HEIGHT = 55; - private final int FOOTER_PADDING = 80; + private final int FOOTER_PADDING = 90; private final int PADDING_3 = 3; private final int PADDING_12 = 12; private final int NUM_CONTROL_BUTTONS = 3; @@ -29,7 +30,7 @@ class EmgSettingsUI extends PApplet implements Runnable { private final int NUM_FOOTER_OBJECTS = 3; private final int FOOTER_OBJECT_WIDTH = 45; private final int FOOTER_OBJECT_HEIGHT = 26; - private int footerObjY = 0; + private int footerObjY; private int[] footerObjX = new int[NUM_FOOTER_OBJECTS]; private final color HEADER_COLOR = OPENBCI_BLUE; @@ -42,14 +43,14 @@ class EmgSettingsUI extends PApplet implements Runnable { public EmgSettingsValues emgSettingsValues; private TextBox channelColumnLabel; - private TextBox smoothLabel; + private TextBox windowLabel; private TextBox uvLimitLabel; private TextBox creepIncLabel; private TextBox creepDecLabel; private TextBox minDeltaUvLabel; private TextBox lowLimitLabel; - private ScrollableList[] smoothLists; + private ScrollableList[] windowLists; private ScrollableList[] uvLimitLists; private ScrollableList[] creepIncLists; private ScrollableList[] creepDecLists; @@ -118,9 +119,11 @@ class EmgSettingsUI extends PApplet implements Runnable { scene(); // Draw header + pushStyle(); noStroke(); fill(HEADER_COLOR); rect(0, 0, width, HEADER_HEIGHT); + popStyle(); emgSettingsValues = dataProcessing.emgSettings.values; @@ -129,7 +132,7 @@ class EmgSettingsUI extends PApplet implements Runnable { //Draw column labels channelColumnLabel.draw(); - smoothLabel.draw(); + windowLabel.draw(); uvLimitLabel.draw(); creepIncLabel.draw(); creepDecLabel.draw(); @@ -139,29 +142,12 @@ class EmgSettingsUI extends PApplet implements Runnable { drawChannelLabels(); //Draw cp5 objects on top of everything - emgCp5.draw(); - } - - private void screenResized() { - x = 0; - y = 0; - w = width; - h = height; - - emgCp5.setGraphics(ourApplet, 0, 0); - - int colWidth = (width / NUM_COLUMNS); - int colOffset = colWidth / 2; - int labelY = y + HEADER_HEIGHT / 2; - channelColumnLabel.setPosition(x + colOffset, labelY); - smoothLabel.setPosition(x + colOffset + colWidth, labelY); - uvLimitLabel.setPosition(x + colOffset + colWidth*2, labelY); - creepIncLabel.setPosition(x + colOffset + colWidth*3, labelY); - creepDecLabel.setPosition(x + colOffset + colWidth*4, labelY); - minDeltaUvLabel.setPosition(x + colOffset + colWidth*5, labelY); - lowLimitLabel.setPosition(x + colOffset + colWidth*6, labelY); - - resizeDropdowns(); + try { + emgCp5.draw(); + } catch (ConcurrentModificationException e) { + e.printStackTrace(); + outputError("EMG Settings UI: Unable to draw cp5 objects."); + } } private void scene() { @@ -169,26 +155,6 @@ class EmgSettingsUI extends PApplet implements Runnable { background(BACKGROUND_COLOR); } - @Override - public void keyReleased() { - - } - - @Override - public void keyPressed() { - - } - - @Override - public void mousePressed() { - - } - - @Override - public void mouseReleased() { - - } - @Override public void exit() { dispose(); @@ -229,7 +195,7 @@ class EmgSettingsUI extends PApplet implements Runnable { for (int i = 0; i < channelCount; i++) { String channelLabel = channelCount > channelLabels.length ? "Channel " + Integer.toString(i + 1) : channelLabels[i]; - text(channelLabel, x + colOffset, dropdownYPositions[i] + (DROPDOWN_HEIGHT / 2)); + text(channelLabel, x + colOffset, dropdownYPositions[i] + (DROPDOWN_HEIGHT / 2) - 2); } popStyle(); @@ -237,15 +203,15 @@ class EmgSettingsUI extends PApplet implements Runnable { private void resizeDropdowns() { dropdownWidth = int((w - (DROPDOWN_SPACER * (NUM_COLUMNS + 1))) / NUM_COLUMNS); - final int MAX_HEIGHT_ITEMS = channelCount == 4 ? 8 : 5; + final int MAX_HEIGHT_ITEMS = 6; for (int i = 0; i < channelCount; i++) { int dropdownX = x + DROPDOWN_SPACER * 2 + dropdownWidth; dropdownYPositions[i] = HEADER_HEIGHT + int(y + ((ROW_HEIGHT) * i) + (((ROW_HEIGHT) - DROPDOWN_HEIGHT) / 2)); final int buttonXIncrement = DROPDOWN_SPACER + dropdownWidth; - smoothLists[i].setPosition(dropdownX, dropdownYPositions[i]); - smoothLists[i].setSize(dropdownWidth, MAX_HEIGHT_ITEMS * DROPDOWN_HEIGHT); + windowLists[i].setPosition(dropdownX, dropdownYPositions[i]); + windowLists[i].setSize(dropdownWidth, MAX_HEIGHT_ITEMS * DROPDOWN_HEIGHT); dropdownX += buttonXIncrement; uvLimitLists[i].setPosition(dropdownX, dropdownYPositions[i]); @@ -270,7 +236,8 @@ class EmgSettingsUI extends PApplet implements Runnable { } private void createAllUIObjects() { - footerObjY = y + h - FOOTER_PADDING + PADDING_3; + 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; @@ -291,13 +258,13 @@ class EmgSettingsUI extends PApplet implements Runnable { int colWidth = (w / NUM_COLUMNS); int colOffset = colWidth / 2; int labelY = y + HEADER_HEIGHT / 2; - channelColumnLabel = new TextBox("Channel", x + colOffset, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP); - smoothLabel = new TextBox("Smooth", x + colOffset + colWidth, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP); - uvLimitLabel = new TextBox("uV Limit", x + colOffset + colWidth*2, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP); - creepIncLabel = new TextBox("Creep +", x + colOffset + colWidth*3, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP); - creepDecLabel = new TextBox("Creep -", x + colOffset + colWidth*4, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP); - minDeltaUvLabel = new TextBox("Min \u0394uV", x + colOffset + colWidth*5, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP); - lowLimitLabel = new TextBox("Low Limit", x + colOffset + colWidth*6, labelY, labelTxt, labelBG, 12, h3, CENTER, TOP); + channelColumnLabel = new TextBox("Channel", x + colOffset, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER); + windowLabel = new TextBox("Window", x + colOffset + colWidth, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER); + uvLimitLabel = new TextBox("uV Limit", x + colOffset + colWidth*2, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER); + creepIncLabel = new TextBox("Creep +", x + colOffset + colWidth*3, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER); + creepDecLabel = new TextBox("Creep -", x + colOffset + colWidth*4, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER); + minDeltaUvLabel = new TextBox("Min \u0394uV", x + colOffset + colWidth*5, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER); + lowLimitLabel = new TextBox("Low Limit", x + colOffset + colWidth*6, labelY, labelTxt, labelBG, 14, h4, CENTER, CENTER); createAllDropdowns(); } @@ -306,7 +273,7 @@ class EmgSettingsUI extends PApplet implements Runnable { //the size and space of these buttons are dependendant on the size of the screen and full ChannelController verbosePrint("EmgChannelSettingsUI: Creating EMG channel setting UI objects..."); - smoothLists = new ScrollableList[channelCount]; + windowLists = new ScrollableList[channelCount]; uvLimitLists = new ScrollableList[channelCount]; creepIncLists = new ScrollableList[channelCount]; creepDecLists = new ScrollableList[channelCount]; @@ -318,7 +285,7 @@ class EmgSettingsUI extends PApplet implements Runnable { //Init dropdowns in reverse so that chan 1 draws on top of chan 2, etc. for (int i = channelCount - 1; i >= 0; i--) { int exgChannel = i; - smoothLists[i] = createDropdown(exgChannel, "smooth_ch_"+(i+1), emgSettingsValues.smoothing[exgChannel].values(), emgSettingsValues.smoothing[exgChannel]); + windowLists[i] = createDropdown(exgChannel, "smooth_ch_"+(i+1), emgSettingsValues.window[exgChannel].values(), emgSettingsValues.window[exgChannel]); uvLimitLists[i] = createDropdown(exgChannel, "uvLimit_ch_"+(i+1), emgSettingsValues.uvLimit[exgChannel].values(), emgSettingsValues.uvLimit[exgChannel]); creepIncLists[i] = createDropdown(exgChannel, "creep_inc_ch_"+(i+1), emgSettingsValues.creepIncreasing[exgChannel].values(), emgSettingsValues.creepIncreasing[exgChannel]); creepDecLists[i] = createDropdown(exgChannel, "creep_dec_ch_"+(i+1), emgSettingsValues.creepDecreasing[exgChannel].values(), emgSettingsValues.creepDecreasing[exgChannel]); @@ -386,9 +353,8 @@ class EmgSettingsUI extends PApplet implements Runnable { EmgSettingsEnum myEnum = (EmgSettingsEnum)bob.get("value"); verbosePrint("EmgSettings: " + (theEvent.getController()).getName() + " == " + myEnum.getString()); - if (myEnum instanceof EmgSmoothing) { - //verbosePrint("HardwareSettings: previousVal == " + emgSettingsValues.previousValues.gain[channel]); - emgSettingsValues.smoothing[channel] = (EmgSmoothing)myEnum; + if (myEnum instanceof EmgWindow) { + emgSettingsValues.window[channel] = (EmgWindow)myEnum; } else if (myEnum instanceof EmgUVLimit) { emgSettingsValues.uvLimit[channel] = (EmgUVLimit)myEnum; } else if (myEnum instanceof EmgCreepIncreasing) { @@ -437,7 +403,7 @@ class EmgSettingsUI extends PApplet implements Runnable { private void updateCp5Objects() { for (int i = 0; i < channelCount; i++) { //Fetch values from the EmgSettingsValues object - EmgSmoothing updateSmoothing = emgSettingsValues.smoothing[i]; + EmgWindow updateSmoothing = emgSettingsValues.window[i]; EmgUVLimit updateUVLimit = emgSettingsValues.uvLimit[i]; EmgCreepIncreasing updateCreepIncreasing = emgSettingsValues.creepIncreasing[i]; EmgCreepDecreasing updateCreepDecreasing = emgSettingsValues.creepDecreasing[i]; @@ -445,7 +411,7 @@ class EmgSettingsUI extends PApplet implements Runnable { EmgLowerThresholdMinimum updateLowerThresholdMinimum = emgSettingsValues.lowerThresholdMinimum[i]; //Update the ScrollableLists - smoothLists[i].getCaptionLabel().setText(updateSmoothing.getString()); + windowLists[i].getCaptionLabel().setText(updateSmoothing.getString()); uvLimitLists[i].getCaptionLabel().setText(updateUVLimit.getString()); creepIncLists[i].getCaptionLabel().setText(updateCreepIncreasing.getString()); creepDecLists[i].getCaptionLabel().setText(updateCreepDecreasing.getString()); diff --git a/OpenBCI_GUI/EmgSettingsValues.pde b/OpenBCI_GUI/EmgSettingsValues.pde index d892b186a..c87d634f8 100644 --- a/OpenBCI_GUI/EmgSettingsValues.pde +++ b/OpenBCI_GUI/EmgSettingsValues.pde @@ -8,7 +8,7 @@ class EmgSettingsValues { //These values can be changed via dropdowns - public EmgSmoothing[] smoothing; + public EmgWindow[] window; public EmgUVLimit[] uvLimit; public EmgCreepIncreasing[] creepIncreasing; public EmgCreepDecreasing[] creepDecreasing; @@ -27,7 +27,7 @@ class EmgSettingsValues { channelCount = currentBoard.getNumEXGChannels(); - smoothing = new EmgSmoothing[channelCount]; + window = new EmgWindow[channelCount]; uvLimit = new EmgUVLimit[channelCount]; creepIncreasing = new EmgCreepIncreasing[channelCount]; creepDecreasing = new EmgCreepDecreasing[channelCount]; @@ -39,7 +39,7 @@ class EmgSettingsValues { lowerThreshold = new float[channelCount]; averageuV = new float[channelCount]; - Arrays.fill(smoothing, EmgSmoothing.ONE_SECOND); + Arrays.fill(window, EmgWindow.ONE_SECOND); Arrays.fill(uvLimit, EmgUVLimit.TWO_HUNDRED_UV); Arrays.fill(creepIncreasing, EmgCreepIncreasing.POINT_9); Arrays.fill(creepDecreasing, EmgCreepDecreasing.POINT_99999); @@ -55,7 +55,7 @@ class EmgSettingsValues { public void process(float[][] data_forDisplay_uV) { //looping over channels and analyzing input data for (int i = 0; i < channelCount; i++) { - float averagePeriod = currentBoard.getSampleRate() * smoothing[i].getValue(); + float averagePeriod = currentBoard.getSampleRate() * window[i].getValue(); int _uvLimit = uvLimit[i].getValue(); float creepSpeedIncreasing = creepIncreasing[i].getValue(); float creepSpeedDecreasing = creepDecreasing[i].getValue(); diff --git a/OpenBCI_GUI/Info.plist.tmpl b/OpenBCI_GUI/Info.plist.tmpl index 1b1cd08d8..5bf524091 100644 --- a/OpenBCI_GUI/Info.plist.tmpl +++ b/OpenBCI_GUI/Info.plist.tmpl @@ -23,7 +23,7 @@ CFBundleShortVersionString 5 CFBundleVersion - 5.2.0 + 5.2.1 CFBundleSignature ???? NSHumanReadableCopyright @@ -32,7 +32,7 @@ Copyright © 2023 OpenBCI CFBundleGetInfoString - June 2023 + July 2023 @@jvm_runtime@@ diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde index f526b9c8d..8433ca6e1 100644 --- a/OpenBCI_GUI/OpenBCI_GUI.pde +++ b/OpenBCI_GUI/OpenBCI_GUI.pde @@ -62,8 +62,8 @@ import java.util.concurrent.atomic.AtomicBoolean; // Global Variables & Instances //------------------------------------------------------------------------ //Used to check GUI version in TopNav.pde and displayed on the splash screen on startup -String localGUIVersionString = "v5.2.0"; -String localGUIVersionDate = "June 2023"; +String localGUIVersionString = "v5.2.1"; +String localGUIVersionDate = "July 2023"; String guiLatestVersionGithubAPI = "https://api.github.com/repos/OpenBCI/OpenBCI_GUI/releases/latest"; String guiLatestReleaseLocation = "https://github.com/OpenBCI/OpenBCI_GUI/releases/latest"; Boolean guiIsUpToDate; diff --git a/OpenBCI_GUI/PopupMessage.pde b/OpenBCI_GUI/PopupMessage.pde index 58dbe7a0a..ad62d8dbf 100644 --- a/OpenBCI_GUI/PopupMessage.pde +++ b/OpenBCI_GUI/PopupMessage.pde @@ -106,7 +106,7 @@ class PopupMessage extends PApplet implements Runnable { textFont(p0, 24); fill(WHITE); textAlign(LEFT, CENTER); - text(headerMessage, (width - w)/2 + padding, (height - h)/2, w, headerHeight); + text(headerMessage, (width - w)/2 + padding, headerHeight/2); //draw message textFont(p3, 16); diff --git a/OpenBCI_GUI/SessionSettings.pde b/OpenBCI_GUI/SessionSettings.pde index 351d92279..cbfe143e7 100644 --- a/OpenBCI_GUI/SessionSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -68,8 +68,8 @@ class SessionSettings { int fftSmoothingSave; int fftFilterSave; //Analog Read settings - int arVertScaleSave; //updates in VertScale_AR() - int arHorizScaleSave; //updates in Duration_AR() + int arVertScaleSave; + int arHorizScaleSave; //Headplot settings int hpIntensitySave; int hpPolaritySave; @@ -189,6 +189,13 @@ class SessionSettings { //Serial load variables int nwSerialBaudRateLoad; + //EMG Widget + List loadEmgActiveChannels = new ArrayList(); + + //EMG Joystick Widget + int loadEmgJoystickSmoothing; + List loadEmgJoystickInputs = new ArrayList(); + //Primary JSON objects for saving and loading data private JSONObject saveSettingsJSONData; private JSONObject loadSettingsJSONData; @@ -204,6 +211,8 @@ class SessionSettings { 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"; //used only in this class to count the number of channels being used while saving/loading, this gets updated in updateToNChan whenever the number of channels being used changes int slnchan; @@ -453,6 +462,30 @@ class SessionSettings { 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(); + int numActiveEMGChan = w_emg.emgChannelSelect.activeChan.size(); + for (int i = 0; i < numActiveEMGChan; i++) { + int activeChan = w_emg.emgChannelSelect.activeChan.get(i); + saveActiveChanEMG.setInt(i, activeChan); + } + saveEMGSettings.setJSONArray("activeChannels", saveActiveChanEMG); + saveSettingsJSONData.setJSONObject(kJSONKeyEmg, saveEMGSettings); + + ///////////////////////////////////////////////Setup new JSON object to save EMG Joystick Settings + JSONObject saveEmgJoystickSettings = new JSONObject(); + saveEmgJoystickSettings.setInt("smoothing", w_emgJoystick.joystickSmoothing.getIndex()); + JSONArray saveEmgJoystickInputs = new JSONArray(); + int numEmgJoystickInputs = w_emgJoystick.emgJoystickInputs.length; + for (int i = 0; i < numEmgJoystickInputs; i++) { + saveEmgJoystickInputs.setInt(i, w_emgJoystick.emgJoystickInputs[i].getIndex()); + } + saveEmgJoystickSettings.setJSONArray("joystickInputs", saveEmgJoystickInputs); + saveSettingsJSONData.setJSONObject(kJSONKeyEmgJoystick, saveEmgJoystickSettings); + ///////////////////////////////////////////////Setup new JSON object to save Widgets Active in respective Containers JSONObject saveWidgetSettings = new JSONObject(); @@ -625,6 +658,23 @@ class SessionSettings { 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)); + } + //get the Widget/Container settings JSONObject loadWidgetSettings = loadSettingsJSONData.getJSONObject(kJSONKeyWidget); //Apply Layout directly before loading and applying widgets to containers @@ -859,6 +909,30 @@ class SessionSettings { break; }//end switch-case for networking settings for all networking protocols + ////////////////////////////Apply EMG widget settings + try { + //apply channel checkbox settings + 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 + 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); + } + //////////////////////////////////////////////////////////// // Apply more loaded widget settings above this line // diff --git a/OpenBCI_GUI/W_EMGJoystick.pde b/OpenBCI_GUI/W_EMGJoystick.pde index 527b9e53b..e825d1be1 100644 --- a/OpenBCI_GUI/W_EMGJoystick.pde +++ b/OpenBCI_GUI/W_EMGJoystick.pde @@ -45,17 +45,31 @@ class W_EMGJoystick extends Widget { private float leftPolarX, leftPolarY; //9:00 private final int EMG_PLOT_OFFSET = 40; //Used to arrange EMG displays outside of X/Y graph - private final String CHANNEL_ONE_LABEL_EN = "Channel 1"; - private final String CHANNEL_TWO_LABEL_EN = "Channel 2"; - private final String CHANNEL_THREE_LABEL_EN = "Channel 3"; - private final String CHANNEL_FOUR_LABEL_EN = "Channel 4"; + private String[] plotChannelLabels = new String[NUM_EMG_CHANNELS]; - private String channelOneLabel; - private String channelTwoLabel; - private String channelThreeLabel; - private String channelFourLabel; + public EmgJoystickSmoothing joystickSmoothing = EmgJoystickSmoothing.POINT_9; - EmgJoystickSmoothing joystickSmoothing = EmgJoystickSmoothing.POINT_9; + private int DROPDOWN_HEIGHT = navH - 4; + private int DROPDOWN_WIDTH = 80; + private int DROPDOWN_SPACER = 10; + private int DROPDOWN_LABEL_WIDTH = 24; + + public EmgJoystickInput[] emgJoystickInputs = new EmgJoystickInput[NUM_EMG_CHANNELS]; + + private ScrollableList xNegativeInputDropdown; + private ScrollableList xPositiveInputDropdown; + private ScrollableList yPositiveInputDropdown; + private ScrollableList yNegativeInputDropdown; + + private TextBox xNegativeInputDropdownLabel; + private TextBox xPositiveInputDropdownLabel; + private TextBox yPositiveInputDropdownLabel; + private TextBox yNegativeInputDropdownLabel; + + private PImage xNegativeInputLabelImage = loadImage("LEFT_100x100.png"); + private PImage xPositiveInputLabelImage = loadImage("RIGHT_100x100.png"); + private PImage yPositiveInputLabelImage = loadImage("UP_100x100.png"); + private PImage yNegativeInputLabelImage = loadImage("DOWN_100x100.png"); W_EMGJoystick(PApplet _parent){ super(_parent); //calls the parent CONSTRUCTOR method of Widget (DON'T REMOVE) @@ -71,13 +85,18 @@ class W_EMGJoystick extends Widget { emgSettingsValues = dataProcessing.emgSettings.values; - channelOneLabel = CHANNEL_ONE_LABEL_EN; - channelTwoLabel = CHANNEL_TWO_LABEL_EN; - channelThreeLabel = CHANNEL_THREE_LABEL_EN; - channelFourLabel = CHANNEL_FOUR_LABEL_EN; + emgJoystickInputs[0] = EmgJoystickInput.CHANNEL_1; + emgJoystickInputs[1] = EmgJoystickInput.CHANNEL_2; + emgJoystickInputs[2] = EmgJoystickInput.CHANNEL_3; + emgJoystickInputs[3] = EmgJoystickInput.CHANNEL_4; + + for (int i = 0; i < NUM_EMG_CHANNELS; i++) { + plotChannelLabels[i] = Integer.toString(emgJoystickInputs[i].getIndex() + 1); + } addDropdown("emgJoystickSmoothingDropdown", "Smoothing", joystickSmoothing.getEnumStringsAsList(), joystickSmoothing.getIndex()); + createInputDropdowns(); } public void update(){ @@ -91,12 +110,12 @@ class W_EMGJoystick extends Widget { drawJoystickXYGraph(); - drawEmgVisualization(0, leftPolarX, leftPolarY); - drawEmgVisualization(1, rightPolarX, rightPolarY); - drawEmgVisualization(2, topPolarX, topPolarY); - drawEmgVisualization(3, bottomPolarX, bottomPolarY); - - drawChannelLabels(); + drawEmgVisualization(emgJoystickInputs[0].getIndex(), leftPolarX, leftPolarY); + drawEmgVisualization(emgJoystickInputs[1].getIndex(), rightPolarX, rightPolarY); + drawEmgVisualization(emgJoystickInputs[2].getIndex(), topPolarX, topPolarY); + drawEmgVisualization(emgJoystickInputs[3].getIndex(), bottomPolarX, bottomPolarY); + + drawInputDropdownLabels(); emgCp5.draw(); } @@ -108,6 +127,7 @@ class W_EMGJoystick extends Widget { emgSettingsButton.setPosition(x0 + 1, y0 + navH + 1); updateJoystickGraphSizeAndPosition(); + updateInputDropdownPositions(); } private void updateJoystickGraphSizeAndPosition() { @@ -183,7 +203,8 @@ class W_EMGJoystick extends Widget { popStyle(); } - + + //This is the core method that updates the joystick input private void updateJoystickInput() { previousJoystickRawX = joystickRawX; previousJoystickRawY = joystickRawY; @@ -193,11 +214,16 @@ class W_EMGJoystick extends Widget { joystickRawY = 0; return; } + + float xNegativeValue = emgSettingsValues.outputNormalized[emgJoystickInputs[0].getIndex()]; + float xPositiveValue = emgSettingsValues.outputNormalized[emgJoystickInputs[1].getIndex()]; + float yPositiveValue = emgSettingsValues.outputNormalized[emgJoystickInputs[2].getIndex()]; + float yNegativeValue = emgSettingsValues.outputNormalized[emgJoystickInputs[3].getIndex()]; - //Here we subtract the values of the left and right channels to get the X axis - joystickRawX = emgSettingsValues.outputNormalized[1] - emgSettingsValues.outputNormalized[0]; - //Here we subtract the values of the top and bottom channels to get the Y axis - joystickRawY = emgSettingsValues.outputNormalized[2] - emgSettingsValues.outputNormalized[3]; + //Here we subtract the value of the right channel from the left channel to get the X axis + joystickRawX = xPositiveValue - xNegativeValue; + //Here we subtract the value of the top channel from the bottom channel to get the Y axis + joystickRawY = yPositiveValue - yNegativeValue; //Map the joystick values to a unit circle float[] unitCircleXY = mapToUnitCircle(joystickRawX, joystickRawY); @@ -266,17 +292,6 @@ class W_EMGJoystick extends Widget { noFill(); rect(barX, barY, BAR_WIDTH, BAR_HEIGHT * -1); - /* - //draw channel number at upper left corner of row/column cell - pushStyle(); - stroke(OPENBCI_DARKBLUE); - fill(OPENBCI_DARKBLUE); - int _chan = index+1; - textFont(p5, 12); - text(_chan + "", 10, 20); - popStyle(); - */ - popStyle(); } @@ -287,11 +302,11 @@ class W_EMGJoystick extends Widget { textFont(p4, 14); textLeading(14); textAlign(CENTER,CENTER); - - text(channelOneLabel, leftPolarX, leftPolarY - BAR_CIRCLE_SPACER * 2); - text(channelTwoLabel, rightPolarX, rightPolarY - BAR_CIRCLE_SPACER *2); - text(channelThreeLabel, topPolarX + BAR_CIRCLE_SPACER * 4, topPolarY); - text(channelFourLabel, bottomPolarX + BAR_CIRCLE_SPACER * 4, bottomPolarY); + + text(plotChannelLabels[0], leftPolarX, leftPolarY - BAR_CIRCLE_SPACER * 2); + text(plotChannelLabels[1], rightPolarX, rightPolarY - BAR_CIRCLE_SPACER *2); + text(plotChannelLabels[2], topPolarX + BAR_CIRCLE_SPACER * 4, topPolarY); + text(plotChannelLabels[3], bottomPolarX + BAR_CIRCLE_SPACER * 4, bottomPolarY); popStyle(); } @@ -314,6 +329,134 @@ class W_EMGJoystick extends Widget { emgSettingsButton.setDescription("Click to open the EMG Settings UI to adjust how this metric is calculated."); } + private ScrollableList createEmgJoystickInputDropdown(String name, EmgJoystickInput joystickInput, int inputNumber) { + ScrollableList list = emgCp5.addScrollableList(name) + .setOpen(false) + .setColorBackground(WHITE) // text field bg color + .setColorValueLabel(OPENBCI_DARKBLUE) // text color + .setColorCaptionLabel(OPENBCI_DARKBLUE) + .setColorForeground(color(125)) // border color when not selected + .setColorActive(BUTTON_PRESSED) // border color when selected + .setOutlineColor(OBJECT_BORDER_GREY) + .setSize(DROPDOWN_WIDTH, DROPDOWN_HEIGHT * 6)//temporary size + .setBarHeight(DROPDOWN_HEIGHT) //height of top/primary bar + .setItemHeight(DROPDOWN_HEIGHT) //height of all item/dropdown bars + .setVisible(true) + ; + // this will store the *actual* enum object inside the dropdown! + for (EmgJoystickInput input : EmgJoystickInput.values()) { + if (input.getIndex() >= currentBoard.getNumEXGChannels()) { + continue; + } + list.addItem(input.getString(), input); + } + //Style the text in the ScrollableList + list.getCaptionLabel() //the caption label is the text object in the primary bar + .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!! + .setText(joystickInput.getString()) + .setFont(h5) + .setSize(12) + .getStyle() //need to grab style before affecting the paddingTop + .setPaddingTop(4) + ; + list.getValueLabel() //the value label is connected to the text objects in the dropdown item bars + .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!! + .setText(joystickInput.getString()) + .setFont(p6) + .setSize(10) //set the font size of the item bars to 14pt + .getStyle() //need to grab style before affecting the paddingTop + .setPaddingTop(3) //4-pixel vertical offset to center text + ; + list.addCallback(new SLCallbackListener(inputNumber)); + return list; + } + + private class SLCallbackListener implements CallbackListener { + private int inputNumber; + + SLCallbackListener(int _i) { + inputNumber = _i; + } + public void controlEvent(CallbackEvent theEvent) { + //Selecting an item from ScrollableList triggers Broadcast + if (theEvent.getAction() == ControlP5.ACTION_BROADCAST) { + int val = (int)(theEvent.getController()).getValue(); + Map bob = ((ScrollableList)theEvent.getController()).getItem(val); + emgJoystickInputs[inputNumber] = (EmgJoystickInput)bob.get("value"); + verbosePrint("EmgJoystickInput: " + (theEvent.getController()).getName() + " == " + emgJoystickInputs[inputNumber].getString()); + + plotChannelLabels[inputNumber] = Integer.toString(emgJoystickInputs[inputNumber].getIndex() + 1); + } + } + } + + private void createInputDropdowns() { + //Create the dropdowns in reverse order so that top dropdown draws over bottom dropdown + yNegativeInputDropdown = createEmgJoystickInputDropdown("yNegativeDropdown", emgJoystickInputs[3], 3); + yPositiveInputDropdown = createEmgJoystickInputDropdown("yPositiveDropdown", emgJoystickInputs[2], 2); + xPositiveInputDropdown = createEmgJoystickInputDropdown("xPositiveDropdown", emgJoystickInputs[1], 1); + xNegativeInputDropdown = createEmgJoystickInputDropdown("xNegativeDropdown", emgJoystickInputs[0], 0); + //Add the dropdowns to the list of cp5 elements to check for mouseover + cp5ElementsToCheck.add(xNegativeInputDropdown); + cp5ElementsToCheck.add(xPositiveInputDropdown); + cp5ElementsToCheck.add(yPositiveInputDropdown); + cp5ElementsToCheck.add(yNegativeInputDropdown); + //Create labels for the dropdowns + color labelBG = color(255,255,255,0); + xNegativeInputDropdownLabel = new TextBox("X-", x, y, OPENBCI_DARKBLUE, WHITE, 12, h3, LEFT, TOP); + xPositiveInputDropdownLabel = new TextBox("X+", x, y, OPENBCI_DARKBLUE, WHITE, 12, h3, LEFT, TOP); + yPositiveInputDropdownLabel = new TextBox("Y+", x, y, OPENBCI_DARKBLUE, WHITE, 12, h3, LEFT, TOP); + yNegativeInputDropdownLabel = new TextBox("Y-", x, y, OPENBCI_DARKBLUE, WHITE, 12, h3, LEFT, TOP); + } + + private void updateInputDropdownPositions(){ + final int Y_AXIS_ARROW_LABEL_WIDTH = DROPDOWN_HEIGHT + DROPDOWN_SPACER; + xNegativeInputDropdown.setPosition((int) (x + navH + DROPDOWN_LABEL_WIDTH), (int) (y + navH + 1)); + xPositiveInputDropdown.setPosition((int) (x + navH + DROPDOWN_LABEL_WIDTH), (int) (y + navH + DROPDOWN_SPACER + DROPDOWN_HEIGHT)); + yPositiveInputDropdown.setPosition((int) (x + w - navH - DROPDOWN_WIDTH - Y_AXIS_ARROW_LABEL_WIDTH), (int) (y + navH + 1)); + yNegativeInputDropdown.setPosition((int) (x + w - navH - DROPDOWN_WIDTH - Y_AXIS_ARROW_LABEL_WIDTH), (int) (y + navH + DROPDOWN_SPACER + DROPDOWN_HEIGHT)); + xNegativeInputDropdownLabel.setPosition((int) xNegativeInputDropdown.getPosition()[0] - DROPDOWN_LABEL_WIDTH, (int) xNegativeInputDropdown.getPosition()[1]); + xPositiveInputDropdownLabel.setPosition((int) xPositiveInputDropdown.getPosition()[0] - DROPDOWN_LABEL_WIDTH, (int) xPositiveInputDropdown.getPosition()[1]); + yPositiveInputDropdownLabel.setPosition((int) yPositiveInputDropdown.getPosition()[0] - DROPDOWN_LABEL_WIDTH, (int) yPositiveInputDropdown.getPosition()[1]); + yNegativeInputDropdownLabel.setPosition((int) yNegativeInputDropdown.getPosition()[0] - DROPDOWN_LABEL_WIDTH, (int) yNegativeInputDropdown.getPosition()[1]); + } + + private void drawInputDropdownLabels() { + xNegativeInputDropdownLabel.draw(); + xPositiveInputDropdownLabel.draw(); + yPositiveInputDropdownLabel.draw(); + yNegativeInputDropdownLabel.draw(); + + pushStyle(); + final int X_OFFSET = DROPDOWN_WIDTH + DROPDOWN_SPACER; + image(xNegativeInputLabelImage, xNegativeInputDropdown.getPosition()[0] + X_OFFSET, xNegativeInputDropdown.getPosition()[1] + 2, DROPDOWN_HEIGHT, DROPDOWN_HEIGHT); + image(xPositiveInputLabelImage, xPositiveInputDropdown.getPosition()[0] + X_OFFSET, xPositiveInputDropdown.getPosition()[1] + 2, DROPDOWN_HEIGHT, DROPDOWN_HEIGHT); + image(yPositiveInputLabelImage, yPositiveInputDropdown.getPosition()[0] + X_OFFSET, yPositiveInputDropdown.getPosition()[1] + 2, DROPDOWN_HEIGHT, DROPDOWN_HEIGHT); + image(yNegativeInputLabelImage, yNegativeInputDropdown.getPosition()[0] + X_OFFSET, yNegativeInputDropdown.getPosition()[1] + 2, DROPDOWN_HEIGHT, DROPDOWN_HEIGHT); + } + + public void updateJoystickInput(int inputNumber, Integer value) { + if (value == null) { + return; + } + emgJoystickInputs[inputNumber] = EmgJoystickInput.values()[value]; + String inputName = emgJoystickInputs[inputNumber].getString(); + switch (inputNumber) { + case 0: + xNegativeInputDropdown.getCaptionLabel().setText(inputName); + break; + case 1: + xPositiveInputDropdown.getCaptionLabel().setText(inputName); + break; + case 2: + yPositiveInputDropdown.getCaptionLabel().setText(inputName); + break; + case 3: + yNegativeInputDropdown.getCaptionLabel().setText(inputName); + break; + } + } + }; public void emgJoystickSmoothingDropdown(int n) { @@ -353,6 +496,57 @@ public enum EmgJoystickSmoothing implements IndexingInterface return value; } + private static List getEnumStringsAsList() { + List enumStrings = new ArrayList(); + for (IndexingInterface val : vals) { + enumStrings.add(val.getString()); + } + return enumStrings; + } +} + +public enum EmgJoystickInput implements IndexingInterface +{ + CHANNEL_1 (0, "Channel 1", 0), + CHANNEL_2 (1, "Channel 2", 1), + CHANNEL_3 (2, "Channel 3", 2), + CHANNEL_4 (3, "Channel 4", 3), + CHANNEL_5 (4, "Channel 5", 4), + CHANNEL_6 (5, "Channel 6", 5), + CHANNEL_7 (6, "Channel 7", 6), + CHANNEL_8 (7, "Channel 8", 7), + CHANNEL_9 (8, "Channel 9", 8), + CHANNEL_10 (9, "Channel 10", 9), + CHANNEL_11 (10, "Channel 11", 10), + CHANNEL_12 (11, "Channel 12", 11), + CHANNEL_13 (12, "Channel 13", 12), + CHANNEL_14 (13, "Channel 14", 13), + CHANNEL_15 (14, "Channel 15", 14), + CHANNEL_16 (15, "Channel 16", 15); + + private int index; + private String name; + private int value; + private static EmgJoystickInput[] vals = values(); + + 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; + } + private static List getEnumStringsAsList() { List enumStrings = new ArrayList(); for (IndexingInterface val : vals) { diff --git a/OpenBCI_GUI/data/DOWN_100x100.png b/OpenBCI_GUI/data/DOWN_100x100.png new file mode 100644 index 000000000..72d8b5de8 Binary files /dev/null and b/OpenBCI_GUI/data/DOWN_100x100.png differ diff --git a/OpenBCI_GUI/data/LEFT_100x100.png b/OpenBCI_GUI/data/LEFT_100x100.png new file mode 100644 index 000000000..1b6b6131f Binary files /dev/null and b/OpenBCI_GUI/data/LEFT_100x100.png differ diff --git a/OpenBCI_GUI/data/RIGHT_100x100.png b/OpenBCI_GUI/data/RIGHT_100x100.png new file mode 100644 index 000000000..56a127893 Binary files /dev/null and b/OpenBCI_GUI/data/RIGHT_100x100.png differ diff --git a/OpenBCI_GUI/data/UP_100x100.png b/OpenBCI_GUI/data/UP_100x100.png new file mode 100644 index 000000000..562d46f0f Binary files /dev/null and b/OpenBCI_GUI/data/UP_100x100.png differ