From 42a17a485f284c80b4bf1428053ad4edfbfdf860 Mon Sep 17 00:00:00 2001 From: "T. van der Zwan" Date: Tue, 24 Sep 2013 11:25:51 +0200 Subject: [PATCH] Created the config tool for defining hyperion json file Former-commit-id: 0eac20fbc633111250911ba422e6edac0ef906e1 --- src/config-tool/.gitignore | 1 + src/config-tool/ConfigTool/.classpath | 6 + src/config-tool/ConfigTool/.gitignore | 2 + src/config-tool/ConfigTool/.project | 17 + .../hyperion/config/HyperionConfigApplet.java | 23 ++ .../org/hyperion/config/LedFrameFactory.java | 165 +++++++++ .../src/org/hyperion/config/LedString.java | 74 ++++ .../src/org/hyperion/config/Main.java | 24 ++ .../hyperion/config/gui/ColorConfigPanel.java | 209 +++++++++++ .../org/hyperion/config/gui/ConfigPanel.java | 206 +++++++++++ .../org/hyperion/config/gui/JHyperionTv.java | 333 ++++++++++++++++++ .../hyperion/config/gui/LedFramePanel.java | 219 ++++++++++++ .../hyperion/config/gui/MiscConfigPanel.java | 142 ++++++++ .../gui/TestImageBBB_01.png.REMOVED.git-id | 1 + .../gui/TestImageBBB_02.png.REMOVED.git-id | 1 + .../gui/TestImageBBB_03.png.REMOVED.git-id | 1 + .../gui/TestImage_01.png.REMOVED.git-id | 1 + .../gui/TestImage_02.png.REMOVED.git-id | 1 + .../gui/TestImage_03.png.REMOVED.git-id | 1 + .../org/hyperion/config/gui/TestImage_04.png | Bin 0 -> 66588 bytes .../gui/TestImage_05.png.REMOVED.git-id | 1 + .../hyperion/config/spec/BootSequence.java | 29 ++ .../org/hyperion/config/spec/BorderSide.java | 22 ++ .../org/hyperion/config/spec/ColorConfig.java | 77 ++++ .../hyperion/config/spec/DeviceConfig.java | 25 ++ .../org/hyperion/config/spec/DeviceType.java | 9 + .../src/org/hyperion/config/spec/Led.java | 21 ++ .../config/spec/LedFrameConstruction.java | 69 ++++ .../org/hyperion/config/spec/MiscConfig.java | 46 +++ .../org/hyperion/config/spec/Ws2801Led.java | 83 +++++ 30 files changed, 1809 insertions(+) create mode 100644 src/config-tool/.gitignore create mode 100644 src/config-tool/ConfigTool/.classpath create mode 100644 src/config-tool/ConfigTool/.gitignore create mode 100644 src/config-tool/ConfigTool/.project create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/HyperionConfigApplet.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/LedFrameFactory.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/LedString.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/Main.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/ColorConfigPanel.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/ConfigPanel.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/JHyperionTv.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/LedFramePanel.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/MiscConfigPanel.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_01.png.REMOVED.git-id create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_02.png.REMOVED.git-id create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_03.png.REMOVED.git-id create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_01.png.REMOVED.git-id create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_02.png.REMOVED.git-id create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_03.png.REMOVED.git-id create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_04.png create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_05.png.REMOVED.git-id create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/spec/BootSequence.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/spec/BorderSide.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/spec/ColorConfig.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/spec/DeviceConfig.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/spec/DeviceType.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/spec/Led.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/spec/LedFrameConstruction.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/spec/MiscConfig.java create mode 100644 src/config-tool/ConfigTool/src/org/hyperion/config/spec/Ws2801Led.java diff --git a/src/config-tool/.gitignore b/src/config-tool/.gitignore new file mode 100644 index 00000000..e8d03113 --- /dev/null +++ b/src/config-tool/.gitignore @@ -0,0 +1 @@ +/.metadata diff --git a/src/config-tool/ConfigTool/.classpath b/src/config-tool/ConfigTool/.classpath new file mode 100644 index 00000000..cd43896c --- /dev/null +++ b/src/config-tool/ConfigTool/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/config-tool/ConfigTool/.gitignore b/src/config-tool/ConfigTool/.gitignore new file mode 100644 index 00000000..77237710 --- /dev/null +++ b/src/config-tool/ConfigTool/.gitignore @@ -0,0 +1,2 @@ +/.settings +/classes diff --git a/src/config-tool/ConfigTool/.project b/src/config-tool/ConfigTool/.project new file mode 100644 index 00000000..9e7af111 --- /dev/null +++ b/src/config-tool/ConfigTool/.project @@ -0,0 +1,17 @@ + + + ConfigTool + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/HyperionConfigApplet.java b/src/config-tool/ConfigTool/src/org/hyperion/config/HyperionConfigApplet.java new file mode 100644 index 00000000..0b5292fe --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/HyperionConfigApplet.java @@ -0,0 +1,23 @@ +package org.hyperion.config; + +import java.awt.BorderLayout; +import java.awt.Dimension; + +import javax.swing.JApplet; + +import org.hyperion.config.gui.ConfigPanel; + +public class HyperionConfigApplet extends JApplet { + + public HyperionConfigApplet() { + super(); + + initialise(); + } + + private void initialise() { + setPreferredSize(new Dimension(600, 300)); + + add(new ConfigPanel(), BorderLayout.CENTER); + } +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/LedFrameFactory.java b/src/config-tool/ConfigTool/src/org/hyperion/config/LedFrameFactory.java new file mode 100644 index 00000000..af326191 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/LedFrameFactory.java @@ -0,0 +1,165 @@ +package org.hyperion.config; + +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.Vector; + +import org.hyperion.config.spec.BorderSide; +import org.hyperion.config.spec.Led; +import org.hyperion.config.spec.LedFrameConstruction; + +public class LedFrameFactory { + + private static int increase(LedFrameConstruction frameSpec, int pLedCounter) { + if (frameSpec.clockwiseDirection) { + return (pLedCounter+1)%frameSpec.getLedCount(); + } else { + if (pLedCounter == 0) { + return frameSpec.getLedCount() - 1; + } + return pLedCounter -1; + } + + } + + public static Vector construct(LedFrameConstruction frameSpec) { + double overlap_frac = 0.50; + + Vector mLeds = new Vector<>(); + + int totalLedCount = frameSpec.getLedCount(); + if (totalLedCount <= 0) { + return mLeds; + } + + int iLed = (totalLedCount - frameSpec.firstLedOffset)%totalLedCount; + if (iLed < 0) { + iLed += totalLedCount; + } + if (frameSpec.topLeftCorner) { + mLeds.add(createLed(frameSpec, iLed, 0.0, 0.0, overlap_frac, BorderSide.top_left)); + iLed = increase(frameSpec, iLed); + } + if (frameSpec.topLedCnt > 0) { + int ledCnt = frameSpec.topLedCnt; + double ledSpacing = (double)1.0/(ledCnt); + for (int iTop=0; iTop 0) { + int ledCnt = frameSpec.rightLedCnt; + double ledSpacing = 1.0/ledCnt; + for (int iRight=0; iRight 0) { + int ledCnt = frameSpec.topLedCnt; + + double ledSpacing = (double)1.0/ledCnt; + for (int iBottom=(ledCnt-1); iBottom>=0; --iBottom) { + if (iBottom > (frameSpec.bottomLedCnt-1)/2 && iBottom < ledCnt - frameSpec.bottomLedCnt/2) { + continue; + } + double led_x = ledSpacing/2.0 + iBottom * ledSpacing; + double led_y = 1.0; + + mLeds.add(createLed(frameSpec, iLed, led_x, led_y, overlap_frac, BorderSide.bottom)); + iLed = increase(frameSpec, iLed); + } + } + if (frameSpec.bottomLeftCorner) { + mLeds.add(createLed(frameSpec, iLed, 0.0, 1.0, overlap_frac, BorderSide.bottom_left)); + iLed = increase(frameSpec, iLed); + } + if (frameSpec.leftLedCnt > 0) { + int ledCnt = frameSpec.leftLedCnt; + double ledSpacing = (double)1.0/ledCnt; + for (int iRight=(ledCnt-1); iRight>=0; --iRight) { + double led_x = 0.0; + double led_y = ledSpacing/2.0 + iRight * ledSpacing; + + mLeds.add(createLed(frameSpec, iLed, led_x, led_y, overlap_frac, BorderSide.left)); + iLed = increase(frameSpec, iLed); + } + } + + return mLeds; + } + + private static Led createLed(LedFrameConstruction frameSpec, int seqNr, double x_frac, double y_frac, double overlap_frac, BorderSide pBorderSide) { + Led led = new Led(); + led.mLedSeqNr = seqNr; + led.mLocation = new Point2D.Double(x_frac, y_frac); + led.mSide = pBorderSide; + + double widthFrac = (1.0/frameSpec.topLedCnt * (1.0 + overlap_frac))/2.0; + double heightFrac = (1.0/frameSpec.leftLedCnt * (1.0 + overlap_frac))/2.0; + + switch (pBorderSide) { + case top_left: { + led.mImageRectangle = new Rectangle2D.Double(0.0, 0.0, frameSpec.verticalDepth, frameSpec.horizontalDepth); + break; + } + case top_right: { + led.mImageRectangle = new Rectangle2D.Double(1.0-frameSpec.verticalDepth, 0.0, frameSpec.verticalDepth, frameSpec.horizontalDepth); + break; + } + case bottom_left: { + led.mImageRectangle = new Rectangle2D.Double(0.0, 1.0-frameSpec.horizontalDepth, frameSpec.verticalDepth, frameSpec.horizontalDepth); + break; + } + case bottom_right: { + led.mImageRectangle = new Rectangle2D.Double(1.0-frameSpec.verticalDepth, 1.0-frameSpec.horizontalDepth, frameSpec.verticalDepth, frameSpec.horizontalDepth); + break; + } + case top:{ + double intXmin_frac = Math.max(0.0, x_frac-widthFrac); + double intXmax_frac = Math.min(x_frac+widthFrac, 1.0); + led.mImageRectangle = new Rectangle2D.Double(intXmin_frac, 0.0, intXmax_frac-intXmin_frac, frameSpec.horizontalDepth); + + break; + } + case bottom: + { + double intXmin_frac = Math.max(0.0, x_frac-widthFrac); + double intXmax_frac = Math.min(x_frac+widthFrac, 1.0); + + led.mImageRectangle = new Rectangle2D.Double(intXmin_frac, 1.0-frameSpec.horizontalDepth, intXmax_frac-intXmin_frac, frameSpec.horizontalDepth); + break; + } + case left: { + double intYmin_frac = Math.max(0.0, y_frac-heightFrac); + double intYmax_frac = Math.min(y_frac+heightFrac, 1.0); + led.mImageRectangle = new Rectangle2D.Double(0.0, intYmin_frac, frameSpec.verticalDepth, intYmax_frac-intYmin_frac); + break; + } + case right: + double intYmin_frac = Math.max(0.0, y_frac-heightFrac); + double intYmax_frac = Math.min(y_frac+heightFrac, 1.0); + led.mImageRectangle = new Rectangle2D.Double(1.0-frameSpec.verticalDepth, intYmin_frac, frameSpec.verticalDepth, intYmax_frac-intYmin_frac); + break; + } + + return led; + } + + +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/LedString.java b/src/config-tool/ConfigTool/src/org/hyperion/config/LedString.java new file mode 100644 index 00000000..c7d3d68f --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/LedString.java @@ -0,0 +1,74 @@ +package org.hyperion.config; + +import java.io.FileWriter; +import java.io.IOException; +import java.util.Locale; +import java.util.Vector; + +import org.hyperion.config.spec.ColorConfig; +import org.hyperion.config.spec.DeviceConfig; +import org.hyperion.config.spec.Led; +import org.hyperion.config.spec.MiscConfig; + +public class LedString { + /** The individual led configuration */ + public Vector leds; + + /** The configuration of the output device */ + DeviceConfig mDeviceConfig = new DeviceConfig(); + + /** The color adjustment configuration */ + ColorConfig mColorConfig = new ColorConfig(); + + /** The miscellaneous configuration (bootsequence, blackborder detector, etc) */ + MiscConfig mMiscConfig = new MiscConfig(); + + public void saveConfigFile(String mFilename) throws IOException { + + try (FileWriter fw = new FileWriter(mFilename)) { + fw.write("// Automatically generated configuration file for 'Hyperion'\n"); + fw.write("// Generated by: 'Hyperion configuration Tool\n"); + fw.write("\n"); + fw.write("{\n"); + + String deviceJson = mDeviceConfig.toJsonString(); + fw.write(deviceJson + ",\n"); + + String colorJson = mColorConfig.toJsonString(); + fw.write(colorJson + ",\n"); + + String ledJson = ledToJsonString(); + fw.write(ledJson + ",\n"); + + String miscJson = mMiscConfig.toJsonString(); + fw.write(miscJson + "\n"); + + fw.write("}\n"); + } catch (IOException e) { + throw e; + } + } + + String ledToJsonString() { + StringBuffer strBuf = new StringBuffer(); + strBuf.append("\t\"leds\" : \n"); + strBuf.append("\t[\n"); + + for (Led led : leds) + { + strBuf.append("\t\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\"index\" : %d,\n", led.mLedSeqNr)); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\"hscan\" : { \"minimum\" : %.4f, \"maximum\" : %.4f },\n", led.mImageRectangle.getMinX(), led.mImageRectangle.getMaxX())); + strBuf.append(String.format(Locale.ROOT, "\t\t\t\"vscan\" : { \"minimum\" : %.4f, \"maximum\" : %.4f }\n", led.mImageRectangle.getMinY(), led.mImageRectangle.getMaxY())); + if (led != leds.lastElement()) { + strBuf.append("\t\t},\n"); + } else { + strBuf.append("\t\t}\n"); + } + } + + strBuf.append("\t]"); + + return strBuf.toString(); + } +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/Main.java b/src/config-tool/ConfigTool/src/org/hyperion/config/Main.java new file mode 100644 index 00000000..2405c8df --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/Main.java @@ -0,0 +1,24 @@ +package org.hyperion.config; + +import javax.swing.JFrame; +import javax.swing.UIManager; + +import org.hyperion.config.gui.ConfigPanel; + +public class Main { + + public static void main(String[] pArgs) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) {} + + JFrame frame = new JFrame(); + frame.setTitle("Hyperion configuration Tool"); + frame.setSize(1300, 600); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + frame.setContentPane(new ConfigPanel()); + + frame.setVisible(true); + } +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/ColorConfigPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/ColorConfigPanel.java new file mode 100644 index 00000000..31b66e5b --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/ColorConfigPanel.java @@ -0,0 +1,209 @@ +package org.hyperion.config.gui; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.GroupLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; + +public class ColorConfigPanel extends JPanel { + private JPanel mRgbTransformPanel; + private JLabel mThresholdLabel; + private JLabel mGammaLabel; + private JLabel mBlacklevelLabel; + private JLabel mWhitelevelLabel; + private JLabel mRedTransformLabel; + private JSpinner mRedThresholdSpinner; + private JSpinner mRedGammaSpinner; + private JSpinner mRedBlacklevelSpinner; + private JSpinner mRedWhitelevelSpinner; + private JLabel mGreenTransformLabel; + private JSpinner mGreenThresholdSpinner; + private JSpinner mGreenGammaSpinner; + private JSpinner mGreenBlacklevelSpinner; + private JSpinner mGreenWhitelevelSpinner; + private JLabel mBlueTransformLabel; + private JSpinner mBlueThresholdSpinner; + private JSpinner mBlueGammaSpinner; + private JSpinner mBlueBlacklevelSpinner; + private JSpinner mBlueWhitelevelSpinner; + + private JPanel mHsvTransformPanel; + private JLabel mSaturationAdjustLabel; + private JSpinner mSaturationAdjustSpinner; + private JLabel mValueAdjustLabel; + private JSpinner mValueAdjustSpinner; + + public ColorConfigPanel() { + super(); + + initialise(); + } + + private void initialise() { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + add(getRgbPanel()); + add(getHsvPanel()); + } + + private JPanel getRgbPanel() { + if (mRgbTransformPanel == null) { + mRgbTransformPanel = new JPanel(); + + GroupLayout layout = new GroupLayout(mRgbTransformPanel); + mRgbTransformPanel.setLayout(layout); + + mThresholdLabel = new JLabel("Thresold"); + mRgbTransformPanel.add(mThresholdLabel); + + mGammaLabel = new JLabel("Gamma"); + mRgbTransformPanel.add(mGammaLabel); + + mBlacklevelLabel = new JLabel("Blacklevel"); + mRgbTransformPanel.add(mBlacklevelLabel); + + mWhitelevelLabel = new JLabel("Whitelevel"); + mRgbTransformPanel.add(mWhitelevelLabel); + + mRedTransformLabel = new JLabel("RED"); + mRgbTransformPanel.add(mRedTransformLabel); + mRedThresholdSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mRedThresholdSpinner); + mRedGammaSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mRedGammaSpinner); + mRedBlacklevelSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mRedBlacklevelSpinner); + mRedWhitelevelSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mRedWhitelevelSpinner); + + mGreenTransformLabel = new JLabel("GREEN"); + mRgbTransformPanel.add(mGreenTransformLabel); + mGreenThresholdSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mGreenThresholdSpinner); + mGreenGammaSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mGreenGammaSpinner); + mGreenBlacklevelSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mGreenBlacklevelSpinner); + mGreenWhitelevelSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mGreenWhitelevelSpinner); + + mBlueTransformLabel = new JLabel("BLUE"); + mRgbTransformPanel.add(mBlueTransformLabel); + mBlueThresholdSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mBlueThresholdSpinner); + mBlueGammaSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mBlueGammaSpinner); + mBlueBlacklevelSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mBlueBlacklevelSpinner); + mBlueWhitelevelSpinner = new JSpinner(new SpinnerNumberModel()); + mRgbTransformPanel.add(mBlueWhitelevelSpinner); + + layout.setHorizontalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mRedTransformLabel) + .addComponent(mGreenTransformLabel) + .addComponent(mBlueTransformLabel)) + .addGroup(layout.createParallelGroup() + .addComponent(mThresholdLabel) + .addComponent(mRedThresholdSpinner) + .addComponent(mGreenThresholdSpinner) + .addComponent(mBlueThresholdSpinner)) + .addGroup(layout.createParallelGroup() + .addComponent(mGammaLabel) + .addComponent(mRedGammaSpinner) + .addComponent(mGreenGammaSpinner) + .addComponent(mBlueGammaSpinner) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mBlacklevelLabel) + .addComponent(mRedBlacklevelSpinner) + .addComponent(mGreenBlacklevelSpinner) + .addComponent(mBlueBlacklevelSpinner) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mWhitelevelLabel) + .addComponent(mRedWhitelevelSpinner) + .addComponent(mGreenWhitelevelSpinner) + .addComponent(mBlueWhitelevelSpinner) + )); + + layout.setVerticalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mThresholdLabel) + .addComponent(mGammaLabel) + .addComponent(mBlacklevelLabel) + .addComponent(mWhitelevelLabel)) + .addGroup(layout.createParallelGroup() + .addComponent(mRedTransformLabel) + .addComponent(mRedThresholdSpinner) + .addComponent(mRedGammaSpinner) + .addComponent(mRedBlacklevelSpinner) + .addComponent(mRedWhitelevelSpinner) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mGreenTransformLabel) + .addComponent(mGreenThresholdSpinner) + .addComponent(mGreenGammaSpinner) + .addComponent(mGreenBlacklevelSpinner) + .addComponent(mGreenWhitelevelSpinner) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mBlueTransformLabel) + .addComponent(mBlueThresholdSpinner) + .addComponent(mBlueGammaSpinner) + .addComponent(mBlueBlacklevelSpinner) + .addComponent(mBlueWhitelevelSpinner) + )); + } + return mRgbTransformPanel; + } + + private JPanel getHsvPanel() { + if (mHsvTransformPanel == null) { + mHsvTransformPanel = new JPanel(); + mHsvTransformPanel.setBorder(BorderFactory.createTitledBorder("HSV")); + + GroupLayout layout = new GroupLayout(mHsvTransformPanel); + mHsvTransformPanel.setLayout(layout); + + mSaturationAdjustLabel = new JLabel("Saturation"); + mHsvTransformPanel.add(mSaturationAdjustLabel); + + mSaturationAdjustSpinner = new JSpinner(new SpinnerNumberModel(1.0, 0.0, 1024.0, 0.01)); + mHsvTransformPanel.add(mSaturationAdjustSpinner); + + mValueAdjustLabel = new JLabel("Value"); + mHsvTransformPanel.add(mValueAdjustLabel); + + mValueAdjustSpinner = new JSpinner(new SpinnerNumberModel(1.0, 0.0, 1024.0, 0.01)); + mHsvTransformPanel.add(mValueAdjustSpinner); + + layout.setHorizontalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mSaturationAdjustLabel) + .addComponent(mValueAdjustLabel) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mSaturationAdjustSpinner) + .addComponent(mValueAdjustSpinner) + ) + ); + + layout.setVerticalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mSaturationAdjustLabel) + .addComponent(mSaturationAdjustSpinner) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mValueAdjustLabel) + .addComponent(mValueAdjustSpinner) + ) + ); + } + return mHsvTransformPanel; + } + +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/ConfigPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/ConfigPanel.java new file mode 100644 index 00000000..cb2bf78b --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/ConfigPanel.java @@ -0,0 +1,206 @@ +package org.hyperion.config.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.util.Observable; +import java.util.Observer; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.GroupLayout; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.hyperion.config.LedFrameFactory; +import org.hyperion.config.LedString; +import org.hyperion.config.spec.LedFrameConstruction; + +public class ConfigPanel extends JPanel { + + private final LedFrameConstruction mLedFrameSpec = new LedFrameConstruction(); + + private final Action mSaveConfigAction = new AbstractAction("Create Hyperion Configuration") { + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.showSaveDialog(ConfigPanel.this); + + LedString ledString = new LedString(); + ledString.leds = LedFrameFactory.construct(mLedFrameSpec); + + try { + ledString.saveConfigFile(fileChooser.getSelectedFile().getAbsolutePath()); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + }; + + private JPanel mTvPanel; + private JHyperionTv mHyperionTv; + + private JPanel mConstructionPanel; + + private JPanel mIntegrationPanel; + + private JLabel mHorizontalDepthLabel; + private JSpinner mHorizontalDepthSpinner; + private JLabel mVerticalDepthLabel; + private JSpinner mVerticalDepthSpinner; + + private JPanel mSpecificationPanel; + + private MiscConfigPanel mMiscPanel; + + private JButton mSaveConfigButton; + + public ConfigPanel() { + super(); + + mLedFrameSpec.clockwiseDirection = true; + + mLedFrameSpec.topLeftCorner = true; + mLedFrameSpec.topRightCorner= true; + mLedFrameSpec.bottomLeftCorner= true; + mLedFrameSpec.bottomRightCorner= true; + + mLedFrameSpec.topLedCnt = 16; + mLedFrameSpec.bottomLedCnt = 16; + mLedFrameSpec.leftLedCnt = 7; + mLedFrameSpec.rightLedCnt = 7; + + mLedFrameSpec.firstLedOffset = 0; + + initialise(); + + mHyperionTv.setLeds(LedFrameFactory.construct(mLedFrameSpec)); + mLedFrameSpec.addObserver(new Observer() { + @Override + public void update(Observable o, Object arg) { + mHyperionTv.setLeds(LedFrameFactory.construct(mLedFrameSpec)); + mHyperionTv.repaint(); + } + }); + } + + private void initialise() { + setLayout(new BorderLayout()); + + add(getTvPanel(), BorderLayout.CENTER); + add(getSpecificationPanel(), BorderLayout.WEST); + + } + + private JPanel getTvPanel() { + if (mTvPanel == null) { + mTvPanel = new JPanel(); + mTvPanel.setLayout(new BorderLayout()); + + mHyperionTv = new JHyperionTv(); + mTvPanel.add(mHyperionTv, BorderLayout.CENTER); + } + return mTvPanel; + } + + private JPanel getSpecificationPanel() { + if (mSpecificationPanel == null) { + mSpecificationPanel = new JPanel(); + mSpecificationPanel.setPreferredSize(new Dimension(300, 200)); + mSpecificationPanel.setLayout(new BoxLayout(mSpecificationPanel, BoxLayout.Y_AXIS)); + + mConstructionPanel = new LedFramePanel(mLedFrameSpec); + mConstructionPanel.setBorder(BorderFactory.createTitledBorder("Construction")); + mSpecificationPanel.add(mConstructionPanel); + + mSpecificationPanel.add(getIntegrationPanel()); + + mMiscPanel = new MiscConfigPanel(); + mMiscPanel.setBorder(BorderFactory.createTitledBorder("Misc")); + mSpecificationPanel.add(mMiscPanel); + + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); + mSaveConfigButton = new JButton(mSaveConfigAction); + panel.add(mSaveConfigButton, BorderLayout.SOUTH); + mSpecificationPanel.add(panel); + } + return mSpecificationPanel; + } + + private JPanel getIntegrationPanel() { + if (mIntegrationPanel == null) { + mIntegrationPanel = new JPanel(); + mIntegrationPanel.setBorder(BorderFactory.createTitledBorder("Image Process")); + + mHorizontalDepthLabel = new JLabel("Horizontal depth:"); + mHorizontalDepthLabel.setPreferredSize(new Dimension(100, 30)); + mHorizontalDepthLabel.setMaximumSize(new Dimension(150, 30)); + mIntegrationPanel.add(mHorizontalDepthLabel); + + mHorizontalDepthSpinner = new JSpinner(new SpinnerNumberModel(0.05, 0.01, 1.0, 0.01)); + mHorizontalDepthSpinner.setPreferredSize(new Dimension(150, 30)); + mHorizontalDepthSpinner.setMaximumSize(new Dimension(250, 30)); + mHorizontalDepthSpinner.addChangeListener(mChangeListener); + mIntegrationPanel.add(mHorizontalDepthSpinner); + + mVerticalDepthLabel = new JLabel("Vertical depth:"); + mVerticalDepthLabel.setPreferredSize(new Dimension(100, 30)); + mVerticalDepthLabel.setMaximumSize(new Dimension(150, 30)); + mIntegrationPanel.add(mVerticalDepthLabel); + + mVerticalDepthSpinner = new JSpinner(new SpinnerNumberModel(0.05, 0.01, 1.0, 0.01)); + mVerticalDepthSpinner.setPreferredSize(new Dimension(150, 30)); + mVerticalDepthSpinner.setMaximumSize(new Dimension(250, 30)); + mVerticalDepthSpinner.addChangeListener(mChangeListener); + mIntegrationPanel.add(mVerticalDepthSpinner); + + GroupLayout layout = new GroupLayout(mIntegrationPanel); + mIntegrationPanel.setLayout(layout); + + layout.setHorizontalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mHorizontalDepthLabel) + .addComponent(mVerticalDepthLabel) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mHorizontalDepthSpinner) + .addComponent(mVerticalDepthSpinner) + ) + ); + layout.setVerticalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mHorizontalDepthLabel) + .addComponent(mHorizontalDepthSpinner) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mVerticalDepthLabel) + .addComponent(mVerticalDepthSpinner) + ) + ); + + } + return mIntegrationPanel; + } + + private final ChangeListener mChangeListener = new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + mLedFrameSpec.horizontalDepth = (Double)mHorizontalDepthSpinner.getValue(); + mLedFrameSpec.verticalDepth = (Double)mVerticalDepthSpinner.getValue(); + + mLedFrameSpec.setChanged(); + mLedFrameSpec.notifyObservers(); + } + }; +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/JHyperionTv.java b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/JHyperionTv.java new file mode 100644 index 00000000..6500e7c2 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/JHyperionTv.java @@ -0,0 +1,333 @@ +package org.hyperion.config.gui; + +import java.awt.BasicStroke; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.net.URL; +import java.util.Vector; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ImageIcon; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JPopupMenu; + +import org.hyperion.config.spec.Led; + +public class JHyperionTv extends Component { + + private JPopupMenu mPopupMenu; + private final Action mLoadAction = new AbstractAction("Load image...") { + JFileChooser mImageChooser; + @Override + public void actionPerformed(ActionEvent e) { + if (mImageChooser == null) { + mImageChooser = new JFileChooser(); + } + + if (mImageChooser.showOpenDialog(JHyperionTv.this) != JFileChooser.APPROVE_OPTION) { + return; + } + File file = mImageChooser.getSelectedFile(); + + try { + ImageIcon imageIcon = new ImageIcon(file.getAbsolutePath()); + Image image = imageIcon.getImage(); + mRawImage = image; + repaint(); + } catch (Exception ex) { + + } + } + }; + + private class SelectImageAction extends AbstractAction { + private final String mImageName; + SelectImageAction(String pImageName) { + super(pImageName); + mImageName = pImageName; + + ImageIcon image = loadImage(); + if (image != null) { + Image scaledImage = image.getImage().getScaledInstance(32, 18, Image.SCALE_SMOOTH); + ImageIcon scaledIcon = new ImageIcon(scaledImage, mImageName); + putValue(SMALL_ICON, scaledIcon); + } + } + @Override + public void actionPerformed(ActionEvent e) { + ImageIcon imageIcon = loadImage(); + if (imageIcon != null) { + mRawImage = imageIcon.getImage(); + repaint(); + } + } + + ImageIcon loadImage() { + URL imageUrl = JHyperionTv.class.getResource(mImageName + ".png"); + if (imageUrl == null) { + System.out.println("Failed to load image: " + mImageName); + return null; + } + return new ImageIcon(imageUrl); + } + } + + private JPopupMenu getPopupMenu() { + if (mPopupMenu == null) { + mPopupMenu = new JPopupMenu(); + mPopupMenu.add(mLoadAction); + + JMenu selectMenu = new JMenu("Select Image"); + selectMenu.add(new SelectImageAction("TestImage_01")); + selectMenu.add(new SelectImageAction("TestImage_02")); + selectMenu.add(new SelectImageAction("TestImage_03")); + selectMenu.add(new SelectImageAction("TestImage_04")); + selectMenu.add(new SelectImageAction("TestImage_05")); + selectMenu.add(new SelectImageAction("TestImageBBB_01")); + selectMenu.add(new SelectImageAction("TestImageBBB_02")); + selectMenu.add(new SelectImageAction("TestImageBBB_03")); + mPopupMenu.add(selectMenu); + } + return mPopupMenu; + } + + private Image mRawImage = new ImageIcon(JHyperionTv.class.getResource("TestImage_01.png")).getImage(); + + int lightBorder = 100; + int tvBorder = 12; + + private class LedWrapper { + public final Led led; + + public int lastX = 0; + public int lastY = 0; + + public LedWrapper(Led pLed) { + led = pLed; + } + } + private final Vector mLeds2 = new Vector<>(); + + public void setLeds(Vector pLeds) { + mLeds2.clear(); + + for (Led led : pLeds) { + mLeds2.add(new LedWrapper(led)); + } + } + + LedWrapper mSelLed = null; + + public JHyperionTv() { + + // Pre-cache the popup menu + getPopupMenu(); + + addMouseMotionListener(new MouseMotionListener() { + @Override + public void mouseMoved(MouseEvent e) { + mSelLed = null; + + double x = (double)(e.getX() - lightBorder - tvBorder) / (getWidth() - lightBorder*2-tvBorder*2); + double y = (double)(e.getY() - lightBorder - tvBorder) / (getHeight() - lightBorder*2-tvBorder*2); + + for (LedWrapper led : mLeds2) { + if (led.led.mImageRectangle.contains(x, y) || (Math.abs(led.led.mLocation.getX() - x) < 0.01 && Math.abs(led.led.mLocation.getY() - y) < 0.01)) { + mSelLed = led; + break; + } + } + + repaint(); + } + @Override + public void mouseDragged(MouseEvent e) { + + } + }); + addMouseListener(new MouseAdapter() { + @Override + public void mouseReleased(MouseEvent e) { + showPopup(e); + } + @Override + public void mousePressed(MouseEvent e) { + showPopup(e); + } + private void showPopup(MouseEvent e) { + if (!e.isPopupTrigger()) { + return; + } + getPopupMenu().show(JHyperionTv.this, e.getX(), e.getY()); + } + }); + } + + @Override + public void paint(Graphics g) { + super.paint(g); + if (getWidth() <= 2*lightBorder+2*tvBorder+10 || getHeight() <= 2*lightBorder+2*tvBorder+10) { + return; + } + + Graphics2D g2d = (Graphics2D) g.create(); + g2d.setColor(Color.BLACK); + g2d.fillRect(0, 0, getWidth(), getHeight()); + + int screenWidth = getWidth() - 2*lightBorder; + int screenHeight = getHeight() - 2*lightBorder; + int imageWidth = screenWidth - 2*tvBorder; + int imageHeight = screenHeight- 2*tvBorder; + + g2d.translate((getWidth() - imageWidth)/2, (getHeight() - imageHeight)/2); + + BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB); + image.getGraphics().drawImage(mRawImage, 0, 0, image.getWidth(), image.getHeight(), null); + + if (mLeds2 != null) { + paintAllLeds(g2d, screenWidth, screenHeight, image); + } + + g2d.setColor(Color.DARK_GRAY.darker()); + g2d.fillRect(-tvBorder, -tvBorder, screenWidth, screenHeight); + g2d.drawImage(image, 0, 0, imageWidth, imageHeight, this); + + paintLedNumbers(g2d); + + for (LedWrapper led : mLeds2) { + g2d.setColor(Color.GRAY); + + int xmin = (int)(led.led.mImageRectangle.getMinX() * (imageWidth-1)); + int xmax = (int)(led.led.mImageRectangle.getMaxX()* (imageWidth-1)); + + int ymin = (int)(led.led.mImageRectangle.getMinY() * (imageHeight-1)); + int ymax = (int)(led.led.mImageRectangle.getMaxY() * (imageHeight-1)); + + g2d.drawRect(xmin, ymin, (xmax-xmin), (ymax-ymin)); + } + if (mSelLed != null) { + g2d.setStroke(new BasicStroke(3.0f)); + g2d.setColor(Color.WHITE); + + int xmin = (int)(mSelLed.led.mImageRectangle.getMinX() * (imageWidth-1)); + int xmax = (int)(mSelLed.led.mImageRectangle.getMaxX()* (imageWidth-1)); + + int ymin = (int)(mSelLed.led.mImageRectangle.getMinY() * (imageHeight-1)); + int ymax = (int)(mSelLed.led.mImageRectangle.getMaxY() * (imageHeight-1)); + + g2d.drawRect(xmin, ymin, (xmax-xmin), (ymax-ymin)); + } + } + + class LedPaint { + int xmin; + int xmax; + int ymin; + int ymax; + + int color; + int seqNr; + + int screenX; + int screenY; + double angle_rad; + } + + private void paintAllLeds(Graphics2D g2d, int screenWidth, int screenHeight, BufferedImage image) { + Dimension screenDimension = new Dimension(getWidth()-2*lightBorder-2*tvBorder, getHeight()-2*lightBorder-2*tvBorder); + + int imageWidth = image.getWidth(); + int imageHeight = image.getHeight(); + + Vector ledPaints = new Vector<>(); + + for (LedWrapper led : mLeds2) { + LedPaint ledPaint = new LedPaint(); + ledPaint.xmin = (int)(led.led.mImageRectangle.getMinX() * (imageWidth-1)); + ledPaint.xmax = (int)(led.led.mImageRectangle.getMaxX()* (imageWidth-1)); + ledPaint.ymin = (int)(led.led.mImageRectangle.getMinY() * (imageHeight-1)); + ledPaint.ymax = (int)(led.led.mImageRectangle.getMaxY() * (imageHeight-1)); + + int red = 0; + int green = 0; + int blue = 0; + int count = 0; + + for (int y = ledPaint.ymin; y <= ledPaint.ymax; ++y) { + for (int x = ledPaint.xmin; x <= ledPaint.xmax; ++x) { + int color = image.getRGB(x, y); + red += (color >> 16) & 0xFF; + green += (color >> 8) & 0xFF; + blue += color & 0xFF; + ++count; + } + } + ledPaint.color = count > 0 ? new Color(red / count, green/count, blue/count).getRGB() : 0; + + ledPaints.add(ledPaint); + + led.lastX = (int) (screenDimension.width * led.led.mLocation.getX()); + led.lastY = (int) (screenDimension.height * led.led.mLocation.getY()); + + ledPaint.screenX = led.lastX; + ledPaint.screenY = led.lastY; + ledPaint.angle_rad = 0.5*Math.PI - led.led.mSide.getAngle(); + + ledPaint.seqNr = led.led.mLedSeqNr; + } + + for (int i=2; i<=180; i+=4) { + int arcSize = 24 + (int)((i/12.0)*(i/12.0)); + + for(LedPaint led : ledPaints) { + int argb = 0x05000000 | (0x00ffffff & led.color); + g2d.setColor(new Color(argb , true)); + + g2d.translate(led.screenX, led.screenY); + g2d.rotate(led.angle_rad); + g2d.fillArc(-arcSize, -arcSize, 2*arcSize, 2*arcSize, 90-(i/2), i); + g2d.rotate(-led.angle_rad); + g2d.translate(-led.screenX, -led.screenY); + } + } + } + + private void paintLedNumbers(Graphics2D pG2d) { + pG2d.setColor(Color.GRAY); + + FontMetrics fontMetrics = pG2d.getFontMetrics(); + for (LedWrapper led : mLeds2) { + String seqNrStr = "" + led.led.mLedSeqNr; + Rectangle2D rect = fontMetrics.getStringBounds(seqNrStr, pG2d); + + pG2d.drawString("" + led.led.mLedSeqNr, (int)(led.lastX-rect.getWidth()/2), (int)(led.lastY+rect.getHeight()/2-2)); + } + } + + public static void main(String[] pArgs) { + JFrame frame = new JFrame(); + frame.setSize(640, 480); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + frame.getContentPane().setLayout(new BorderLayout()); + frame.getContentPane().add(new JHyperionTv()); + + frame.setVisible(true); + } +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/LedFramePanel.java b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/LedFramePanel.java new file mode 100644 index 00000000..0197d453 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/LedFramePanel.java @@ -0,0 +1,219 @@ +package org.hyperion.config.gui; + +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.GroupLayout; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.hyperion.config.spec.DeviceType; +import org.hyperion.config.spec.LedFrameConstruction; + +public class LedFramePanel extends JPanel { + + private final LedFrameConstruction mLedFrameSpec; + + private JLabel mTypeLabel; + private JComboBox mTypeCombo; + + private JLabel mHorizontalCountLabel; + private JSpinner mHorizontalCountSpinner; + private JLabel mBottomGapCountLabel; + private JSpinner mBottomGapCountSpinner; + + private JLabel mVerticalCountLabel; + private JSpinner mVerticalCountSpinner; + + private JLabel mTopCornerLabel; + private JComboBox mTopCornerCombo; + private JLabel mBottomCornerLabel; + private JComboBox mBottomCornerCombo; + + private JLabel mDirectionLabel; + private JComboBox mDirectionCombo; + + private JLabel mOffsetLabel; + private JSpinner mOffsetSpinner; + + public LedFramePanel(LedFrameConstruction ledFrameSpec) { + super(); + + mLedFrameSpec = ledFrameSpec; + + initialise(); + } + + private void initialise() { + mTypeLabel = new JLabel("LED Type:"); + mTypeLabel.setPreferredSize(new Dimension(100, 30)); + mTypeLabel.setMaximumSize(new Dimension(150, 30)); + add(mTypeLabel); + mTypeCombo = new JComboBox<>(DeviceType.values()); + mTypeCombo.addActionListener(mActionListener); + mTypeCombo.setPreferredSize(new Dimension(150, 30)); + mTypeCombo.setMaximumSize(new Dimension(250, 30)); + add(mTypeCombo); + + mTopCornerLabel = new JLabel("Led in top corners"); + mTopCornerLabel.setPreferredSize(new Dimension(100, 30)); + mTopCornerLabel.setMaximumSize(new Dimension(150, 30)); + add(mTopCornerLabel); + mTopCornerCombo = new JComboBox<>(new Boolean[] {true, false}); + mTopCornerCombo.addActionListener(mActionListener); + mTopCornerCombo.setPreferredSize(new Dimension(150, 30)); + mTopCornerCombo.setMaximumSize(new Dimension(250, 30)); + add(mTopCornerCombo); + + mBottomCornerLabel = new JLabel("Led in bottom corners"); + mBottomCornerLabel.setPreferredSize(new Dimension(100, 30)); + mBottomCornerLabel.setMaximumSize(new Dimension(150, 30)); + add(mBottomCornerLabel); + mBottomCornerCombo = new JComboBox<>(new Boolean[] {true, false}); + mBottomCornerCombo.addActionListener(mActionListener); + mBottomCornerCombo.setPreferredSize(new Dimension(150, 30)); + mBottomCornerCombo.setMaximumSize(new Dimension(250, 30)); + add(mBottomCornerCombo); + + mDirectionLabel = new JLabel("Direction"); + mDirectionLabel.setPreferredSize(new Dimension(100, 30)); + mDirectionLabel.setMaximumSize(new Dimension(150, 30)); + add(mDirectionLabel); + mDirectionCombo = new JComboBox<>(LedFrameConstruction.Direction.values()); + mDirectionCombo.addActionListener(mActionListener); + mDirectionCombo.setPreferredSize(new Dimension(150, 30)); + mDirectionCombo.setMaximumSize(new Dimension(250, 30)); + add(mDirectionCombo); + + mHorizontalCountLabel = new JLabel("Horizontal #:"); + mHorizontalCountLabel.setPreferredSize(new Dimension(100, 30)); + mHorizontalCountLabel.setMaximumSize(new Dimension(150, 30)); + add(mHorizontalCountLabel); + mHorizontalCountSpinner = new JSpinner(new SpinnerNumberModel(mLedFrameSpec.topLedCnt, 0, 1024, 1)); + mHorizontalCountSpinner.addChangeListener(mChangeListener); + mHorizontalCountSpinner.setPreferredSize(new Dimension(150, 30)); + mHorizontalCountSpinner.setMaximumSize(new Dimension(250, 30)); + add(mHorizontalCountSpinner); + + mBottomGapCountLabel = new JLabel("Bottom Gap #:"); + mBottomGapCountLabel.setPreferredSize(new Dimension(100, 30)); + mBottomGapCountLabel.setMaximumSize(new Dimension(150, 30)); + add(mBottomGapCountLabel); + mBottomGapCountSpinner = new JSpinner(new SpinnerNumberModel(mLedFrameSpec.topLedCnt - mLedFrameSpec.bottomLedCnt, 0, 1024, 1)); + mBottomGapCountSpinner.addChangeListener(mChangeListener); + mBottomGapCountSpinner.setPreferredSize(new Dimension(150, 30)); + mBottomGapCountSpinner.setMaximumSize(new Dimension(250, 30)); + add(mBottomGapCountSpinner); + + mVerticalCountLabel = new JLabel("Vertical #:"); + mVerticalCountLabel.setPreferredSize(new Dimension(100, 30)); + mVerticalCountLabel.setMaximumSize(new Dimension(150, 30)); + add(mVerticalCountLabel); + mVerticalCountSpinner = new JSpinner(new SpinnerNumberModel(mLedFrameSpec.rightLedCnt, 0, 1024, 1)); + mVerticalCountSpinner.addChangeListener(mChangeListener); + mVerticalCountSpinner.setPreferredSize(new Dimension(150, 30)); + mVerticalCountSpinner.setMaximumSize(new Dimension(250, 30)); + add(mVerticalCountSpinner); + + mOffsetLabel = new JLabel("1st LED offset"); + mOffsetLabel.setPreferredSize(new Dimension(100, 30)); + mOffsetLabel.setMaximumSize(new Dimension(150, 30)); + add(mOffsetLabel); + mOffsetSpinner = new JSpinner(new SpinnerNumberModel(mLedFrameSpec.firstLedOffset, Integer.MIN_VALUE, Integer.MAX_VALUE, 1)); + mOffsetSpinner.addChangeListener(mChangeListener); + mOffsetSpinner.setPreferredSize(new Dimension(150, 30)); + mOffsetSpinner.setMaximumSize(new Dimension(250, 30)); + add(mOffsetSpinner); + + GroupLayout layout = new GroupLayout(this); + layout.setAutoCreateGaps(true); + setLayout(layout); + + layout.setHorizontalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mTypeLabel) + .addComponent(mDirectionLabel) + .addComponent(mTopCornerLabel) + .addComponent(mBottomCornerLabel) + .addComponent(mHorizontalCountLabel) + .addComponent(mBottomGapCountLabel) + .addComponent(mVerticalCountLabel) + .addComponent(mOffsetLabel)) + .addGroup(layout.createParallelGroup() + .addComponent(mTypeCombo) + .addComponent(mDirectionCombo) + .addComponent(mTopCornerCombo) + .addComponent(mBottomCornerCombo) + .addComponent(mHorizontalCountSpinner) + .addComponent(mBottomGapCountSpinner) + .addComponent(mVerticalCountSpinner) + .addComponent(mOffsetSpinner)) + ); + layout.setVerticalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mTypeLabel) + .addComponent(mTypeCombo)) + .addGroup(layout.createParallelGroup() + .addComponent(mDirectionLabel) + .addComponent(mDirectionCombo)) + .addGroup(layout.createParallelGroup() + .addComponent(mTopCornerLabel) + .addComponent(mTopCornerCombo)) + .addGroup(layout.createParallelGroup() + .addComponent(mBottomCornerLabel) + .addComponent(mBottomCornerCombo)) + .addGroup(layout.createParallelGroup() + .addComponent(mHorizontalCountLabel) + .addComponent(mHorizontalCountSpinner)) + .addGroup(layout.createParallelGroup() + .addComponent(mVerticalCountLabel) + .addComponent(mVerticalCountSpinner)) + .addGroup(layout.createParallelGroup() + .addComponent(mBottomGapCountLabel) + .addComponent(mBottomGapCountSpinner)) + .addGroup(layout.createParallelGroup() + .addComponent(mOffsetLabel) + .addComponent(mOffsetSpinner))); + + } + + void updateLedConstruction() { + mLedFrameSpec.topLeftCorner = (Boolean)mTopCornerCombo.getSelectedItem(); + mLedFrameSpec.topRightCorner = (Boolean)mTopCornerCombo.getSelectedItem(); + mLedFrameSpec.bottomLeftCorner = (Boolean)mBottomCornerCombo.getSelectedItem(); + mLedFrameSpec.bottomRightCorner = (Boolean)mBottomCornerCombo.getSelectedItem(); + + mLedFrameSpec.clockwiseDirection = ((LedFrameConstruction.Direction)mDirectionCombo.getSelectedItem()) == LedFrameConstruction.Direction.clockwise; + mLedFrameSpec.firstLedOffset = (Integer)mOffsetSpinner.getValue(); + + mLedFrameSpec.topLedCnt = (Integer)mHorizontalCountSpinner.getValue(); + mLedFrameSpec.bottomLedCnt = Math.max(0, mLedFrameSpec.topLedCnt - (Integer)mBottomGapCountSpinner.getValue()); + mLedFrameSpec.rightLedCnt = (Integer)mVerticalCountSpinner.getValue(); + mLedFrameSpec.leftLedCnt = (Integer)mVerticalCountSpinner.getValue(); + + mLedFrameSpec.setChanged(); + mLedFrameSpec.notifyObservers(); + + mBottomGapCountSpinner.setValue(mLedFrameSpec.topLedCnt - mLedFrameSpec.bottomLedCnt); + } + + private final ActionListener mActionListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + updateLedConstruction(); + } + }; + private final ChangeListener mChangeListener = new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + updateLedConstruction(); + } + }; + +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/MiscConfigPanel.java b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/MiscConfigPanel.java new file mode 100644 index 00000000..5146b62c --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/MiscConfigPanel.java @@ -0,0 +1,142 @@ +package org.hyperion.config.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.GroupLayout; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import org.hyperion.config.spec.BootSequence; + +public class MiscConfigPanel extends JPanel { + + private JLabel mMenuLabel; + private JComboBox mMenuCombo; + private JLabel mVideoLabel; + private JComboBox mVideoCombo; + private JLabel mPictureLabel; + private JComboBox mPictureCombo; + private JLabel mAudioLabel; + private JComboBox mAudioCombo; + + private JLabel mBlackborderDetectorLabel; + private JComboBox mBlackborderDetectorCombo; + private JLabel mBootSequenceLabel; + private JComboBox mBootSequenceCombo; + + public MiscConfigPanel() { + super(); + + initialise(); + } + + private void initialise() { + GroupLayout layout = new GroupLayout(this); + layout.setAutoCreateGaps(true); + setLayout(layout); + + mMenuLabel = new JLabel("XBMC Menu"); + add(mMenuLabel); + + mMenuCombo = new JComboBox<>(new String[] {"On", "Off"}); + mMenuCombo.setSelectedItem("Off"); + mMenuCombo.setToolTipText("Enables('On') or disbales('Off') the ambi-light in the XBMC Menu"); + mMenuCombo.addActionListener(mActionListener); + add(mMenuCombo); + + mVideoLabel = new JLabel("Video"); + add(mVideoLabel); + + mVideoCombo = new JComboBox<>(new String[] {"On", "Off"}); + mVideoCombo.setSelectedItem("On"); + mVideoCombo.setToolTipText("Enables('On') or disbales('Off') the ambi-light during video playback"); + mVideoCombo.addActionListener(mActionListener); + add(mVideoCombo); + + mPictureLabel = new JLabel("Picture"); + add(mPictureLabel); + + mPictureCombo = new JComboBox<>(new String[] {"On", "Off"}); + mPictureCombo.setSelectedItem("Off"); + mPictureCombo.setToolTipText("Enables('On') or disbales('Off') the ambi-light when viewing pictures"); + mPictureCombo.addActionListener(mActionListener); + add(mPictureCombo); + + mAudioLabel = new JLabel("Audio"); + add(mAudioLabel); + + mAudioCombo = new JComboBox<>(new String[] {"On", "Off"}); + mAudioCombo.setSelectedItem("Off"); + mAudioCombo.setToolTipText("Enables('On') or disbales('Off') the ambi-light when listing to audio"); + mAudioCombo.addActionListener(mActionListener); + add(mAudioCombo); + + mBlackborderDetectorLabel = new JLabel("Blackborder Detector:"); + add(mBlackborderDetectorLabel); + + mBlackborderDetectorCombo = new JComboBox<>(new String[] {"On", "Off"}); + mBlackborderDetectorCombo.setSelectedItem("On"); + mBlackborderDetectorCombo.setToolTipText("Enables or disables the blackborder detection and removal"); + add(mBlackborderDetectorCombo); + + mBootSequenceLabel = new JLabel("Boot Sequence:"); + add(mBootSequenceLabel); + + mBootSequenceCombo = new JComboBox<>(BootSequence.values()); + mBootSequenceCombo.setSelectedItem(BootSequence.rainbow); + mBootSequenceCombo.setToolTipText("The sequence used on startup to verify proper working of all the leds"); + add(mBootSequenceCombo); + + layout.setHorizontalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mMenuLabel) + .addComponent(mVideoLabel) + .addComponent(mPictureLabel) + .addComponent(mAudioLabel) + .addComponent(mBlackborderDetectorLabel) + .addComponent(mBootSequenceLabel) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mMenuCombo) + .addComponent(mVideoCombo) + .addComponent(mPictureCombo) + .addComponent(mAudioCombo) + .addComponent(mBlackborderDetectorCombo) + .addComponent(mBootSequenceCombo) + )); + layout.setVerticalGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup() + .addComponent(mMenuLabel) + .addComponent(mMenuCombo) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mVideoLabel) + .addComponent(mVideoCombo) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mPictureLabel) + .addComponent(mPictureCombo) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mAudioLabel) + .addComponent(mAudioCombo) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mBlackborderDetectorLabel) + .addComponent(mBlackborderDetectorCombo) + ) + .addGroup(layout.createParallelGroup() + .addComponent(mBootSequenceLabel) + .addComponent(mBootSequenceCombo) + )); + } + + private final ActionListener mActionListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + + } + }; +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_01.png.REMOVED.git-id b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_01.png.REMOVED.git-id new file mode 100644 index 00000000..ca813483 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_01.png.REMOVED.git-id @@ -0,0 +1 @@ +176f5c8e4a406929620764b19e77c5120570325c \ No newline at end of file diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_02.png.REMOVED.git-id b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_02.png.REMOVED.git-id new file mode 100644 index 00000000..b3f62ec5 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_02.png.REMOVED.git-id @@ -0,0 +1 @@ +048b062d931b7754a533734a1dec21692bce2dc6 \ No newline at end of file diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_03.png.REMOVED.git-id b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_03.png.REMOVED.git-id new file mode 100644 index 00000000..5f5afb77 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImageBBB_03.png.REMOVED.git-id @@ -0,0 +1 @@ +bd23dc9ed11283d527effc78e437e8ef18903123 \ No newline at end of file diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_01.png.REMOVED.git-id b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_01.png.REMOVED.git-id new file mode 100644 index 00000000..202fe705 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_01.png.REMOVED.git-id @@ -0,0 +1 @@ +427d6ffe9935a3011f05b13bcf44a47796e99a80 \ No newline at end of file diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_02.png.REMOVED.git-id b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_02.png.REMOVED.git-id new file mode 100644 index 00000000..4d26fccf --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_02.png.REMOVED.git-id @@ -0,0 +1 @@ +0dd04b5ec932aa251136740c6ea87f71847eb537 \ No newline at end of file diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_03.png.REMOVED.git-id b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_03.png.REMOVED.git-id new file mode 100644 index 00000000..bad5b106 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_03.png.REMOVED.git-id @@ -0,0 +1 @@ +10fa576b5a494b9e0377e8b944904bfa7ac97cb6 \ No newline at end of file diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_04.png b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_04.png new file mode 100644 index 0000000000000000000000000000000000000000..6106e5fa3b3246769d524bac2c8d5d348c75591e GIT binary patch literal 66588 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 literal 0 HcmV?d00001 diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_05.png.REMOVED.git-id b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_05.png.REMOVED.git-id new file mode 100644 index 00000000..e388ef7e --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/gui/TestImage_05.png.REMOVED.git-id @@ -0,0 +1 @@ +620cf4f8a4c8dc62cabe9045c1ad04c38cecb942 \ No newline at end of file diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/spec/BootSequence.java b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/BootSequence.java new file mode 100644 index 00000000..805eb576 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/BootSequence.java @@ -0,0 +1,29 @@ +package org.hyperion.config.spec; + +public enum BootSequence { + rainbow, + knight_rider, + none; + + public static BootSequence fromString(String pStr) { + for (BootSequence seq : values()) { + if (seq.toString().equalsIgnoreCase(pStr)) { + return seq; + } + } + return none; + } + + @Override + public String toString() { + switch(this) { + case rainbow: + return "Rainbow"; + case knight_rider: + return "Kinght Rider"; + case none: + return "None"; + } + return "None"; + } +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/spec/BorderSide.java b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/BorderSide.java new file mode 100644 index 00000000..a0bd5675 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/BorderSide.java @@ -0,0 +1,22 @@ +package org.hyperion.config.spec; + +public enum BorderSide { + top_left (0.75*Math.PI), + top(0.5*Math.PI), + top_right(0.25*Math.PI), + right(0.0*Math.PI), + bottom_right(-0.25*Math.PI), + bottom(-0.5*Math.PI), + bottom_left(-0.75*Math.PI), + left(1.0*Math.PI); + + private final double mAngle; + + BorderSide(double pAngle) { + mAngle = pAngle; + } + + public double getAngle() { + return mAngle; + } +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/spec/ColorConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/ColorConfig.java new file mode 100644 index 00000000..0df65857 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/ColorConfig.java @@ -0,0 +1,77 @@ +package org.hyperion.config.spec; + +import java.util.Locale; + +public class ColorConfig { + + double mSaturationGain = 1.0; + double mValueGain = 1.0; + + double mRedThreshold = 0.0; + double mRedGamma = 1.0; + double mRedBlacklevel = 0.0; + double mRedWhitelevel = 1.0; + + double mGreenThreshold = 0.0; + double mGreenGamma = 1.0; + double mGreenBlacklevel = 0.0; + double mGreenWhitelevel = 1.0; + + double mBlueThreshold = 0.0; + double mBlueGamma = 1.0; + double mBlueBlacklevel = 0.0; + double mBlueWhitelevel = 1.0; + + public String toJsonString() { + StringBuffer strBuf = new StringBuffer(); + strBuf.append("\t\"color\" :\n"); + strBuf.append("\t{\n"); + strBuf.append(hsvToJsonString() + ",\n"); + strBuf.append(rgbToJsonString() + "\n"); + strBuf.append("\t}"); + + return strBuf.toString(); + } + + public String hsvToJsonString() { + StringBuffer strBuf = new StringBuffer(); + strBuf.append("\t\t\"hsv\" :\n"); + strBuf.append("\t\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\tsaturationGain : %.4f,\n", mSaturationGain)); + strBuf.append(String.format(Locale.ROOT, "\t\t\tsaturationGain : %.4f\n", mValueGain)); + + strBuf.append("\t\t}"); + return strBuf.toString(); + } + + public String rgbToJsonString() { + StringBuffer strBuf = new StringBuffer(); + + strBuf.append("\t\t\"red\" :\n"); + strBuf.append("\t\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\tthreshold : %.4f,\n", mRedThreshold)); + strBuf.append(String.format(Locale.ROOT, "\t\t\tgamma : %.4f,\n", mRedGamma)); + strBuf.append(String.format(Locale.ROOT, "\t\t\tblacklevel : %.4f,\n", mRedBlacklevel)); + strBuf.append(String.format(Locale.ROOT, "\t\t\twhitelevel : %.4f\n", mRedWhitelevel)); + strBuf.append("\t\t},\n"); + + strBuf.append("\t\t\"green\" :\n"); + strBuf.append("\t\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\tthreshold : %.4f,\n", mGreenThreshold)); + strBuf.append(String.format(Locale.ROOT, "\t\t\tgamma : %.4f,\n", mGreenGamma)); + strBuf.append(String.format(Locale.ROOT, "\t\t\tblacklevel : %.4f,\n", mGreenBlacklevel)); + strBuf.append(String.format(Locale.ROOT, "\t\t\twhitelevel : %.4f\n", mGreenWhitelevel)); + strBuf.append("\t\t},\n"); + + strBuf.append("\t\t\"blue\" :\n"); + strBuf.append("\t\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\tthreshold : %.4f,\n", mBlueThreshold)); + strBuf.append(String.format(Locale.ROOT, "\t\t\tgamma : %.4f,\n", mBlueGamma)); + strBuf.append(String.format(Locale.ROOT, "\t\t\tblacklevel : %.4f,\n", mBlueBlacklevel)); + strBuf.append(String.format(Locale.ROOT, "\t\t\twhitelevel : %.4f\n", mBlueWhitelevel)); + strBuf.append("\t\t}"); + + return strBuf.toString(); + } + +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/spec/DeviceConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/DeviceConfig.java new file mode 100644 index 00000000..a04e4c17 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/DeviceConfig.java @@ -0,0 +1,25 @@ +package org.hyperion.config.spec; + +public class DeviceConfig { + + String mName = "MyPi"; + DeviceType mType = DeviceType.ws2801; + String mOutput = "/dev/spidev0.0"; + int mBaudrate = 48000; + + public String toJsonString() { + StringBuffer strBuf = new StringBuffer(); + strBuf.append("\t\"device\" :\n"); + strBuf.append("\t{\n"); + + strBuf.append("\t\t\"name\" : \"").append(mName).append("\",\n"); + strBuf.append("\t\t\"type\" : \"").append(mType).append("\",\n"); + strBuf.append("\t\t\"output\" : \"").append(mOutput).append("\",\n"); + strBuf.append("\t\t\"rate\" : ").append(mBaudrate).append("\n"); + + strBuf.append("\t}"); + + return strBuf.toString(); + } + +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/spec/DeviceType.java b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/DeviceType.java new file mode 100644 index 00000000..f3cfa8ee --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/DeviceType.java @@ -0,0 +1,9 @@ +package org.hyperion.config.spec; + +public enum DeviceType { + ws2801, + test, + none; + + +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/spec/Led.java b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/Led.java new file mode 100644 index 00000000..8135b3fc --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/Led.java @@ -0,0 +1,21 @@ +package org.hyperion.config.spec; + +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + + +public class Led { + + public int mLedSeqNr; + + public BorderSide mSide; + + public Point2D mLocation; + + public Rectangle2D mImageRectangle; + + @Override + public String toString() { + return "Led[" + mLedSeqNr + "] Location=" + mLocation + " Rectangle=" + mImageRectangle; + } +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/spec/LedFrameConstruction.java b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/LedFrameConstruction.java new file mode 100644 index 00000000..8adc320c --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/LedFrameConstruction.java @@ -0,0 +1,69 @@ +package org.hyperion.config.spec; + +import java.util.Observable; + + + +/** + * The LedFrame describes the construction of leds along the sides of the TV screen. + * + */ +public class LedFrameConstruction extends Observable { + + public enum Direction { + clockwise, + counter_clockwise; + } + + /** True if the leds are organised clockwise else false (counter clockwise) */ + public boolean clockwiseDirection; + + /** True if the top left corner has a led else false */ + public boolean topLeftCorner; + /** True if the top right corner has a led else false */ + public boolean topRightCorner; + /** True if the bottom left corner has a led else false */ + public boolean bottomLeftCorner; + /** True if the bottom right corner has a led else false */ + public boolean bottomRightCorner; + + /** The number of leds between the top-left corner and the top-right corner of the screen + (excluding the corner leds) */ + public int topLedCnt; + /** The number of leds between the bottom-left corner and the bottom-right corner of the screen + (excluding the corner leds) */ + public int bottomLedCnt; + + /** The number of leds between the top-left corner and the bottom-left corner of the screen + (excluding the corner leds) */ + public int leftLedCnt; + /** The number of leds between the top-right corner and the bottom-right corner of the screen + (excluding the corner leds) */ + public int rightLedCnt; + + /** The offset (in leds) of the starting led counted clockwise from the top-left corner */ + public int firstLedOffset; + + /** The 'integration depth' of the leds along the horizontal axis of the tv */ + public double horizontalDepth = 0.05; + /** The 'integration depth' of the leds along the vertical axis of the tv */ + public double verticalDepth = 0.05; + + /** The fraction of overlap from one to another led */ + public double overlapFraction = 0.0; + + public int getLedCount() { + int cornerLedCnt = 0; + if (topLeftCorner) ++cornerLedCnt; + if (topRightCorner) ++cornerLedCnt; + if (bottomLeftCorner) ++cornerLedCnt; + if (bottomRightCorner) ++cornerLedCnt; + + return topLedCnt + bottomLedCnt + leftLedCnt + rightLedCnt + cornerLedCnt; + } + + @Override + public void setChanged() { + super.setChanged(); + } +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/spec/MiscConfig.java b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/MiscConfig.java new file mode 100644 index 00000000..82e43e78 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/MiscConfig.java @@ -0,0 +1,46 @@ +package org.hyperion.config.spec; + +import java.util.Locale; + + +public class MiscConfig { + BootSequence mBootSequence = BootSequence.rainbow; + int mBootSequenceLength_ms = 3000; + + boolean mBlackborderDetector = true; + + int mFrameGrabberWidth = 64; + int mFrameGrabberHeight = 64; + int mFrameGrabberInterval_ms = 100; + + boolean mXbmcChecker = true; + String mXbmcAddress = "127.0.0.1"; + int mXbmcTcpPort = 9090; + + public String toJsonString() { + StringBuffer strBuf = new StringBuffer(); + strBuf.append("\t\"bootsequence\" :\n"); + strBuf.append("\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\"type\" : \"%s\",\n", mBootSequence)); + strBuf.append(String.format(Locale.ROOT, "\t\t\"duration_ms\" : %d\n", mBootSequenceLength_ms)); + strBuf.append("\t},\n"); + + strBuf.append("\t\"framegrabber\" :\n"); + strBuf.append("\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\"width\" : %d,\n", mFrameGrabberWidth)); + strBuf.append(String.format(Locale.ROOT, "\t\t\"height\" : %d,\n", mFrameGrabberHeight)); + strBuf.append(String.format(Locale.ROOT, "\t\t\"frequency_Hz\" : %.1f\n", 1000.0/mFrameGrabberInterval_ms)); + strBuf.append("\t},\n"); + + strBuf.append("\t\"xbmcVideoChecker\" :\n"); + strBuf.append("\t{\n"); + strBuf.append(String.format(Locale.ROOT, "\t\t\"enable\" : %s,\n", mXbmcChecker)); + strBuf.append(String.format(Locale.ROOT, "\t\t\"xbmcAddress\" : \"%s\",\n", mXbmcAddress)); + strBuf.append(String.format(Locale.ROOT, "\t\t\"xbmcTcpPort\" : %d\n", mXbmcTcpPort)); + strBuf.append("\t}"); + + return strBuf.toString(); + } + + +} diff --git a/src/config-tool/ConfigTool/src/org/hyperion/config/spec/Ws2801Led.java b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/Ws2801Led.java new file mode 100644 index 00000000..ec46f0d6 --- /dev/null +++ b/src/config-tool/ConfigTool/src/org/hyperion/config/spec/Ws2801Led.java @@ -0,0 +1,83 @@ +package org.hyperion.config.spec; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; + +import javax.swing.JComponent; + +public class Ws2801Led extends JComponent { + + enum Border { + top, + topleft, + left, + bottomleft, + bottom, + bottomright, + right, + topright; + } + + final int size = 12; + final int halfSize = size/2; + + final Border mBorder; + + public Ws2801Led(Border pBorder) { + mBorder = pBorder; + } + + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g.create(); + +// g2d.setColor(Color.BLACK); +// g2d.drawRect(0, 0, getWidth()-1, getHeight()-1); + + switch (mBorder) { + case top: + g2d.translate(getWidth()/2, getHeight()); + break; + case topleft: + g2d.translate(getWidth(), getHeight()); + g2d.rotate(1.75*Math.PI); + break; + case left: + g2d.translate(0, getHeight()/2); + g2d.rotate(Math.PI*0.5); + break; + case bottomleft: + g2d.translate(getWidth(), 0); + g2d.rotate(-0.75*Math.PI); + break; + case bottom: + g2d.translate(getWidth()/2, 0); + g2d.rotate(Math.PI*1.0); + break; + case bottomright: + g2d.rotate(0.75*Math.PI); + break; + case right: + g2d.translate(getWidth(), getHeight()/2); + g2d.rotate(Math.PI*1.5); + break; + case topright: + g2d.translate(0, getHeight()); + g2d.rotate(0.25*Math.PI); + break; + } + + g2d.setColor(new Color(255, 0, 0, 172)); + g2d.fillRoundRect(-3, -12, 6, 6, 2, 2); + + g2d.setColor(new Color(255, 0, 0, 255)); + g2d.drawRoundRect(-3, -12, 6, 6, 2, 2); + + g2d.setColor(Color.GRAY); + g2d.drawRect(-halfSize, -halfSize, size, halfSize); + g2d.fillRect(-halfSize, -halfSize, size, halfSize); + + } +}