diff --git a/.classpath b/.classpath index fb50116..4c8d782 100644 --- a/.classpath +++ b/.classpath @@ -1,6 +1,7 @@ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b2dfb01 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright Tomas Uktveris (c) 2015 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index a42847b..d02e7b1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,46 @@ -DotMatrixJava -============= +8x8x8 Led Cube control program +--------- -Java PC animation generator for 3D8S 8x8x8 Led charlieplexed +PC control program for generic 8x8x8 3D LED Cube found on eBay +(e.g. 3D LightSquared 8x8x8 LED Cube DIY kit, ideasoft, etc.) + +![Program view](/help/program_view.png) + +* Written in Java (requires Java RE, download here: http://java.com) +* Supports direct animation playback through Serial/UART interface (rxtx library) +* Various GUI usability enhancements + +##### Firmware +This program can control any packet format compatible LED cube (see below). +Example firmware of a compatible LED cube with an STC12C5A60S2 MCU can be found here: [Source Code](https://github.com/tomazas/ledcube8x8x8) + +##### Using the program +* Run run_x32.bat for 32-bit Windows +* Run run_x64.bat for 64-bit Windows + +Check this YouTube video for example: https://youtu.be/UplJi7pdV_Y +[![Using program](http://img.youtube.com/vi/UplJi7pdV_Y/0.jpg)](https://youtu.be/UplJi7pdV_Y) + +##### LED Cube control packet format + +8x8x8 LED Cubes that support below packet format can be controlled with the program via Serial console or other MCU such as an Atmega/Arduino. +Example UART/Serial packet (in hex): +``` +F2 +00 00 00 00 00 00 00 FF +00 00 00 00 00 00 00 FF +00 00 00 00 00 00 00 FF +00 00 00 00 00 00 00 FF +00 00 00 00 00 00 00 FF +00 00 00 00 00 00 00 FF +00 00 00 00 00 00 00 FF +00 00 00 00 00 00 00 FF +``` + +* F2 - denotes packet header (aka. batch update) +* next 64 bytes - (8x8x8 bits) of LED light states + * one byte - controls a LED row (8 LEDs) + * can be any value in range: 00-FF (i.e. 00 - all 8 LEDs in row are off, FF - all 8 LEDs are on) + * a single line (e.g. 00 00 00 00 00 00 00 FF) denotes a 64 LED layer + +`Implementation note:` to save energy/current consumption only a single layer (64 LEDs) in a LED cube is ON at one time. This is done for all layers and so fast (using a hardware timer), that the human eye does not recognize this. This hack allows to view the cube (all layers) as fully lit. diff --git a/arduino-controller/SDAnimation.ino b/arduino-controller/SDAnimation.ino index c5b5679..d201ed3 100644 --- a/arduino-controller/SDAnimation.ino +++ b/arduino-controller/SDAnimation.ino @@ -1,18 +1,15 @@ #include "SD.h" #include "string.h" -Sd2Card card; -SdVolume volume; -const int chipSelect = 4; File root; void setup() { - Serial.begin(57600); + Serial.begin(9600); - pinMode(10, OUTPUT); // change this to 53 on a mega + pinMode(10, OUTPUT); // SS pin must be output, change this to 53 on a mega - bool sd_ok = SD.begin(9); + bool sd_ok = SD.begin(9); // chip select pin if (!sd_ok) { @@ -25,22 +22,13 @@ void setup() void sendAnimation(byte *data) { - Serial.write(0xf3); - Serial.write(data[1]); - - Serial.write(0xf4); - Serial.write(data[2]); - - Serial.write(0xf5); - Serial.write(data[3]); - - Serial.write(0xf2); + Serial.write(0xf2); // batch update supported for (byte i = 8; i < 72; i++) { Serial.write(data[i]); } - delay(makeWord(data[6], data[7])); + delay(20); } void readAnimation(File & file) @@ -61,7 +49,7 @@ void loop(void) char *p = file.name(); char *p_dot = strchr(p, '.'); - if (p_dot != NULL && strcmp(p_dot, ".DAT") == 0) + if (p_dot != NULL && strcmp(p_dot, ".dat") == 0) { readAnimation(file); } diff --git a/dotmatrixjava.jar b/dotmatrixjava.jar index 1670f59..80adff4 100644 Binary files a/dotmatrixjava.jar and b/dotmatrixjava.jar differ diff --git a/examples.zip b/examples.zip deleted file mode 100644 index dd5b0d2..0000000 Binary files a/examples.zip and /dev/null differ diff --git a/help/program_view.png b/help/program_view.png new file mode 100644 index 0000000..bd41783 Binary files /dev/null and b/help/program_view.png differ diff --git a/libs/RXTXcomm.jar b/libs/RXTXcomm.jar new file mode 100644 index 0000000..e1e7503 Binary files /dev/null and b/libs/RXTXcomm.jar differ diff --git a/record.dat b/record.dat deleted file mode 100644 index 541b6da..0000000 Binary files a/record.dat and /dev/null differ diff --git a/record2.dat b/record2.dat deleted file mode 100644 index 749cbdc..0000000 Binary files a/record2.dat and /dev/null differ diff --git a/record3.dat b/record3.dat deleted file mode 100644 index ab5c616..0000000 Binary files a/record3.dat and /dev/null differ diff --git a/run_x32.bat b/run_x32.bat new file mode 100644 index 0000000..e248c1f --- /dev/null +++ b/run_x32.bat @@ -0,0 +1 @@ +java -Djava.library.path=./runtime/x32 -jar dotmatrixjava.jar \ No newline at end of file diff --git a/run_x64.bat b/run_x64.bat new file mode 100644 index 0000000..92403b9 --- /dev/null +++ b/run_x64.bat @@ -0,0 +1 @@ +java -Djava.library.path=./runtime/x64 -jar dotmatrixjava.jar \ No newline at end of file diff --git a/runtime/x32/rxtxParallel.dll b/runtime/x32/rxtxParallel.dll new file mode 100644 index 0000000..28d4d2a Binary files /dev/null and b/runtime/x32/rxtxParallel.dll differ diff --git a/runtime/x32/rxtxSerial.dll b/runtime/x32/rxtxSerial.dll new file mode 100644 index 0000000..5cd55bd Binary files /dev/null and b/runtime/x32/rxtxSerial.dll differ diff --git a/runtime/x64/rxtxParallel.dll b/runtime/x64/rxtxParallel.dll new file mode 100644 index 0000000..92666dd Binary files /dev/null and b/runtime/x64/rxtxParallel.dll differ diff --git a/runtime/x64/rxtxSerial.dll b/runtime/x64/rxtxSerial.dll new file mode 100644 index 0000000..211e006 Binary files /dev/null and b/runtime/x64/rxtxSerial.dll differ diff --git a/src/aguegu/dotmatrix/DMRecordFrame.java b/src/aguegu/dotmatrix/DMRecordFrame.java index 92b3f12..aa26b7c 100644 --- a/src/aguegu/dotmatrix/DMRecordFrame.java +++ b/src/aguegu/dotmatrix/DMRecordFrame.java @@ -85,6 +85,15 @@ public byte[] getData() { return data; } + + public byte[] getSimpleData() { + byte[] data = new byte[65]; + + data[0] = (byte) 0xf2; + System.arraycopy(dm.getCache(), 0, data, 1, DotMatrix.CACHE_LENGTH); + + return data; + } public String getCacheString() { return DotMatrix.cacheString(getData()); diff --git a/src/aguegu/dotmatrix/DMRecordHeaderPanel.java b/src/aguegu/dotmatrix/DMRecordHeaderPanel.java index 53a52a5..85bf2b9 100644 --- a/src/aguegu/dotmatrix/DMRecordHeaderPanel.java +++ b/src/aguegu/dotmatrix/DMRecordHeaderPanel.java @@ -1,157 +1,307 @@ package aguegu.dotmatrix; +import gnu.io.CommPortIdentifier; +import gnu.io.PortInUseException; +import gnu.io.SerialPort; +import gnu.io.UnsupportedCommOperationException; + import java.awt.FlowLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; import java.util.ResourceBundle; +import java.util.Timer; +import java.util.TimerTask; import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; -import javax.swing.ImageIcon; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; import javax.swing.JCheckBox; +import javax.swing.JComboBox; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; -import javax.swing.JRadioButtonMenuItem; -import javax.swing.JSlider; +import javax.swing.JTextField; import javax.swing.border.EmptyBorder; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; public class DMRecordHeaderPanel extends JPanel { private static final long serialVersionUID = 7530602456593370095L; private DMRecordPanel parent; - private JSlider sliderBrightness; - private JSlider sliderSpan; - - private JCheckBox checkboxUpperLed; - private JCheckBox checkboxBottomLed; - - private JRadioButtonMenuItem[] radiobuttonModes; - - private CL cl; + private SerialPort port; + private OutputStream outputStream; + private JComboBox baudBox; + private JComboBox portBox; + private Timer timer; + private JTextField delayBox; + private JCheckBox loopChk; - public DMRecordHeaderPanel(DMRecordPanel dmrp, ResourceBundle res) { + public DMRecordHeaderPanel(DMRecordPanel dmrp, final ResourceBundle res) { parent = dmrp; this.setLayout(new FlowLayout(FlowLayout.LEFT)); this.setBorder(new EmptyBorder(new Insets(0, 4, 0, 0))); - + JPanel panelMode = new JPanel(new FlowLayout(FlowLayout.LEFT)); - - radiobuttonModes = new JRadioButtonMenuItem[3]; - ButtonGroup bgMode = new ButtonGroup(); - int i = 0; - for (DMMode mode : DMMode.values()) { - radiobuttonModes[i] = new JRadioButtonMenuItem( - new ImageIcon(getClass().getResource( - "/image/" + mode.toString().toLowerCase() + ".png"))); - - radiobuttonModes[i].setActionCommand(mode.toString()); - radiobuttonModes[i].addActionListener(new ActionListenerMode()); - bgMode.add(radiobuttonModes[i]); - panelMode.add(radiobuttonModes[i]); - i++; - } + panelMode.setAlignmentX(LEFT_ALIGNMENT); JPanel panelAttachment = new JPanel(new FlowLayout(FlowLayout.LEFT)); - checkboxUpperLed = new JCheckBox(res.getString("upper_led")); - checkboxUpperLed.addActionListener(new ActionListenerAttachment()); - panelAttachment.add(checkboxUpperLed); + panelAttachment.add(new JLabel(res.getString("comport") + ":")); + + portBox = new JComboBox(enumeratePorts().toArray(new String[]{})); + portBox.setEditable(true); + + panelAttachment.add(portBox); + + final JButton refreshBtn = new JButton(res.getString("refresh")); + refreshBtn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + portBox.removeAllItems(); + portBox.setModel(new DefaultComboBoxModel(enumeratePorts().toArray(new String[]{}))); + } + }); + panelAttachment.add(refreshBtn); + + panelAttachment.add(new JLabel(res.getString("baud") + ":")); + baudBox = new JComboBox(new String[]{"9600","19200","38400","57600","115200"}); + baudBox.setEditable(true); + panelAttachment.add(baudBox); + + final JButton playBtn = new JButton(res.getString("play")); + playBtn.setEnabled(false); + + final JButton closeBtn = new JButton(res.getString("close_serial")); + closeBtn.setEnabled(false); + + final JButton openBtn = new JButton(res.getString("open_serial")); + openBtn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (portBox.getSelectedItem() == null) return; + String input = ((String)portBox.getSelectedItem()).trim(); + if (!input.isEmpty()) { + if (openSerial(input)) { + closeBtn.setEnabled(true); + openBtn.setEnabled(false); + playBtn.setEnabled(true); + } + } + } + }); + panelAttachment.add(openBtn); + panelAttachment.add(closeBtn); + + closeBtn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + closeSerial(); + closeBtn.setEnabled(false); + openBtn.setEnabled(true); + playBtn.setEnabled(false); + } + }); - checkboxBottomLed = new JCheckBox(res.getString("bottom_led")); - checkboxBottomLed.addActionListener(new ActionListenerAttachment()); - panelAttachment.add(checkboxBottomLed); - panelAttachment.setAlignmentX(LEFT_ALIGNMENT); + playBtn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (timer != null) { + timer.cancel(); + timer = null; + playBtn.setText(res.getString("play")); + closeBtn.setEnabled(true); + return; + } + + int delay = 20; // default ms + try { + delay = Integer.parseInt(delayBox.getText()); + if (delay <= 0) { + throw new NumberFormatException("Not a positive number"); + } + } catch (NumberFormatException ex) { + JOptionPane.showMessageDialog(null, "Invalid frame update delay!", "Play error", JOptionPane.ERROR_MESSAGE); + return; + } + + if (parent.getNumFrames() > 0) { + TimerTask task = new TimerTask() { + private int counter = 0; + @Override + public void run() { + int num = parent.getNumFrames(); + if (num > 0) { + // still have frames left + counter = (counter+1)%num; + parent.setFrame(counter); + + try { + byte data[] = parent.getRecordFrame().getSimpleData(); + outputStream.write(data); + } catch (IOException e) { + this.cancel(); + JOptionPane.showMessageDialog(null, "Unable to play:\n"+e.getMessage(), "Play error", JOptionPane.ERROR_MESSAGE); + } + } else { + // e.g. new project was created and all frames are gone! + timer.cancel(); + timer = null; + playBtn.setText(res.getString("play")); + closeBtn.setEnabled(true); + } + } + }; + + if (loopChk.isSelected()) { + playBtn.setText(res.getString("stop")); + closeBtn.setEnabled(false); + + timer = new Timer(); + timer.schedule(task, 0, delay); + } else { + task.run(); + } + } else { + JOptionPane.showMessageDialog(null, "Nothing to play - add frames first!", "Play error", JOptionPane.ERROR_MESSAGE); + } + } + }); JPanel panelModeAndAttachment = new JPanel(); - panelModeAndAttachment.setLayout(new BoxLayout(panelModeAndAttachment, - BoxLayout.Y_AXIS)); - panelModeAndAttachment.add(new JLabel(res.getString("mode") + ":")); + panelModeAndAttachment.setLayout(new BoxLayout(panelModeAndAttachment, BoxLayout.Y_AXIS)); panelModeAndAttachment.add(panelMode); panelModeAndAttachment.add(panelAttachment); - cl = new CL(); - - sliderBrightness = new JSlider(0, 255, 255); - sliderBrightness.setMinorTickSpacing(0x20); - sliderBrightness.setMajorTickSpacing(0x40); - sliderBrightness.setPaintTicks(true); - sliderBrightness.setSnapToTicks(true); - sliderBrightness.addChangeListener(cl); - sliderBrightness.setAlignmentX(LEFT_ALIGNMENT); - - sliderSpan = new JSlider(0, 0x0800, 0x0080); - sliderSpan.setMinorTickSpacing(0x10); - sliderSpan.setSnapToTicks(true); - sliderSpan.setPaintTicks(true); - sliderSpan.addChangeListener(cl); - sliderSpan.setAlignmentX(LEFT_ALIGNMENT); - + JPanel panelBelow = new JPanel(new FlowLayout(FlowLayout.LEFT)); + panelBelow.add(new JLabel(res.getString("delay") + ": ")); + + delayBox = new JTextField(res.getString("delay_ms"), 10); + panelBelow.add(delayBox); + panelBelow.add(playBtn); + + loopChk = new JCheckBox(res.getString("anim_loop"), true); + panelBelow.add(playBtn); + panelBelow.add(loopChk); + panelModeAndAttachment.add(panelBelow); + JPanel panelSliders = new JPanel(); panelSliders.setLayout(new BoxLayout(panelSliders, BoxLayout.Y_AXIS)); - panelSliders.add(new JLabel(res.getString("brightness") + ":")); - panelSliders.add(sliderBrightness); - panelSliders.add(new JLabel(res.getString("time_span") + ":")); - panelSliders.add(sliderSpan); this.add(panelModeAndAttachment); this.add(panelSliders); } - - private class CL implements ChangeListener { - @Override - public void stateChanged(ChangeEvent e) { - if (e.getSource() instanceof JSlider) { - if (e.getSource().equals(sliderBrightness)) { - parent.getRecordFrame().setBrightness( - (Integer) sliderBrightness.getValue()); - } else if (e.getSource().equals(sliderSpan)) { - parent.getRecordFrame().setSpan( - (Integer) sliderSpan.getValue()); - } - - parent.refresh(true); + + public void updateParent(DMRecordPanel parent) { + this.parent = parent; + } + + public List enumeratePorts() { + // scan available COM ports + List ports = new ArrayList(); + System.out.println("enumerate serial ports"); + + try { + Enumeration port_list = CommPortIdentifier.getPortIdentifiers(); + + while (port_list.hasMoreElements()) { + CommPortIdentifier port_id = (CommPortIdentifier) port_list.nextElement(); + if (port_id.getPortType() == CommPortIdentifier.PORT_SERIAL) { + ports.add(port_id.getName()); + } + } + } catch (UnsatisfiedLinkError e) { + JOptionPane.showMessageDialog(null, "Error initializing serial port:\n" + e.getMessage(), "Serial error", JOptionPane.ERROR_MESSAGE); + } finally { + System.out.println(ports.size()+" ports found: " + ports); + if (ports.isEmpty()) { + ports.add("No ports found!"); // default } } + + return ports; } - - private class ActionListenerAttachment implements ActionListener { - @Override - public void actionPerformed(ActionEvent e) { - if (checkboxUpperLed.isSelected() && checkboxBottomLed.isSelected()) - parent.getRecordFrame().setAttachment(DMAttachment.BOTH); - else if (checkboxUpperLed.isSelected()) - parent.getRecordFrame().setAttachment(DMAttachment.UPPER_LED); - else if (checkboxBottomLed.isSelected()) - parent.getRecordFrame().setAttachment(DMAttachment.BOTTOM_LED); - else - parent.getRecordFrame().setAttachment(DMAttachment.NONE); - parent.refresh(true); + + public boolean openSerial(String name) { + try { + System.out.println("open serial: " + name); + + Enumeration port_list = CommPortIdentifier.getPortIdentifiers(); + boolean found = false; + + while (port_list.hasMoreElements()) { + // Get the list of ports + CommPortIdentifier port_id = (CommPortIdentifier) port_list.nextElement(); + + if (port_id.getPortType() == CommPortIdentifier.PORT_SERIAL && port_id.getName().equals(name)) { + found = true; + + try { + // attempt to open + port = (SerialPort) port_id.open("PortListOpen", 20); + if (port == null) { + throw new Exception("Cannot open port: " + name); + } + + System.out.println("serial port opened: " + name); + + int baudRate = Integer.parseInt((String)baudBox.getSelectedItem()); + port.setSerialPortParams( + baudRate, + SerialPort.DATABITS_8, + SerialPort.STOPBITS_1, + SerialPort.PARITY_NONE); + port.setDTR(true); + + outputStream = port.getOutputStream(); + return true; + } catch (UnsupportedCommOperationException e) { + JOptionPane.showMessageDialog(null, "Invalid serial parameters:\n" + e.getMessage(), "Serial error", JOptionPane.ERROR_MESSAGE); + } catch (PortInUseException e) { + String owner = port_id.getCurrentOwner(); + JOptionPane.showMessageDialog(null, "The port is already in use! Owner: " + (owner != null ? owner : "unknown"), + "Serial error", JOptionPane.ERROR_MESSAGE); + } catch (Exception e) { + JOptionPane.showMessageDialog(null, "I/O error:\n" + e.getMessage(), "Serial error", JOptionPane.ERROR_MESSAGE); + } + } + } + + // not found + if (!found) { + JOptionPane.showMessageDialog(null, "Serial port not found: " + name, "Serial error", JOptionPane.ERROR_MESSAGE); + } + } catch (UnsatisfiedLinkError e) { + JOptionPane.showMessageDialog(null, "Error initializing serial port:\n" + e.getMessage(), "Serial error", JOptionPane.ERROR_MESSAGE); } - } - - private class ActionListenerMode implements ActionListener { - @Override - public void actionPerformed(ActionEvent e) { - parent.getRecordFrame().setMode( - DMMode.getMode(e.getActionCommand())); - parent.refresh(true); + + return false; + } + + private void closeSerial() { + if (port != null) { + System.out.println("closing serial port"); + port.close(); } + port = null; + outputStream = null; } public void refresh() { - sliderBrightness.removeChangeListener(cl); - sliderBrightness.setValue(parent.getRecordFrame().getBrightness()); - sliderBrightness.addChangeListener(cl); + //sliderBrightness.removeChangeListener(cl); + //sliderBrightness.setValue(parent.getRecordFrame().getBrightness()); + //sliderBrightness.addChangeListener(cl); - sliderSpan.removeChangeListener(cl); - sliderSpan.setValue(parent.getRecordFrame().getSpan()); - sliderSpan.addChangeListener(cl); + //sliderSpan.removeChangeListener(cl); + //sliderSpan.setValue(parent.getRecordFrame().getSpan()); + //sliderSpan.addChangeListener(cl); - switch (parent.getRecordFrame().getAttachment()) { + /*switch (parent.getRecordFrame().getAttachment()) { case BOTH: checkboxUpperLed.setSelected(true); checkboxBottomLed.setSelected(true); @@ -172,7 +322,7 @@ public void refresh() { } radiobuttonModes[parent.getRecordFrame().getMode().ordinal()] - .setSelected(true); + .setSelected(true);*/ } } diff --git a/src/aguegu/dotmatrix/DMRecordList.java b/src/aguegu/dotmatrix/DMRecordList.java index 888bd97..831a5bd 100644 --- a/src/aguegu/dotmatrix/DMRecordList.java +++ b/src/aguegu/dotmatrix/DMRecordList.java @@ -32,6 +32,10 @@ public void syncToReocrd() { lm.addElement(dmrf); } } + + public int getNumFrames() { + return lm.getSize(); + } class DotMatrixRecordCellRender extends JLabel implements ListCellRenderer { diff --git a/src/aguegu/dotmatrix/DMRecordPanel.java b/src/aguegu/dotmatrix/DMRecordPanel.java index 8b3609a..cb9db04 100644 --- a/src/aguegu/dotmatrix/DMRecordPanel.java +++ b/src/aguegu/dotmatrix/DMRecordPanel.java @@ -49,7 +49,7 @@ public class DMRecordPanel extends JPanel { private static final String[] FRAME_OPERATION_COMMANDS = new String[] { "on", "off", "x+", "x-", "y+", "y-", "z+", "z-", "3c", "3a", "2c", - "2a", "1c", "1a", "0c", "0a", "xf", "yf", "zf", "r" }; + "2a", "1c", "1a", "0c", "0a", "r" }; private boolean inLoop = true; private static Font monoFont; @@ -58,9 +58,11 @@ public class DMRecordPanel extends JPanel { private JPanel panelFrameOperation; private ResourceBundle res; + private DotMatrixTest parent; - public DMRecordPanel(ResourceBundle res) { + public DMRecordPanel(DotMatrixTest parent, DMRecordPanel prev, ResourceBundle res) { this.res = res; + this.parent = parent; this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); panelController = new JPanel(); @@ -84,8 +86,9 @@ public DMRecordPanel(ResourceBundle res) { ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); panelController.add(textAreaPane); - panelHeader = new DMRecordHeaderPanel(this, res); - + + panelHeader = (prev != null) ? prev.getHeader() : new DMRecordHeaderPanel(this, res); + panelHeader.updateParent(this); panelController.add(panelHeader); this.add(panelController); @@ -96,6 +99,18 @@ public DMRecordPanel(ResourceBundle res) { initMenu(); initFrameOperationPanel(); } + + public DMRecordHeaderPanel getHeader() { + return panelHeader; + } + + public void setFrame(int index) { + parent.setActiveFrame(index); + } + + public int getNumFrames() { + return parent.getNumFrames(); + } public void setFrame(DMRecordFrame dmrf) { this.dmrf = new DMRecordFrame(dmrf.getIndex()); @@ -250,7 +265,7 @@ public void initFrameOperationPanel() { panelFrameOperation.add(button); } - checkboxInLoop = new JCheckBox("", inLoop); + checkboxInLoop = new JCheckBox(res.getString("loop"), inLoop); checkboxInLoop.addActionListener(new ActionListenerInLoop()); checkboxInLoop.setToolTipText(res.getString("loop")); panelFrameOperation.add(checkboxInLoop); @@ -319,15 +334,6 @@ public void actionPerformed(ActionEvent e) { case "0a": dm.rotate(0, false, recycle); break; - case "xf": - dm.flip(DotMatrix.Direction.X_POSI); - break; - case "yf": - dm.flip(DotMatrix.Direction.Y_POSI); - break; - case "zf": - dm.flip(DotMatrix.Direction.Z_POSI); - break; } refresh(true); } diff --git a/src/aguegu/dotmatrix/DotMatrix.java b/src/aguegu/dotmatrix/DotMatrix.java index 3535fbf..68e721f 100644 --- a/src/aguegu/dotmatrix/DotMatrix.java +++ b/src/aguegu/dotmatrix/DotMatrix.java @@ -76,6 +76,8 @@ static public String cacheString(byte[] cache) { String s = new String(); for (int i = 0; i < cache.length; i++) { + if (i > 0 && i < 8) continue; // skip irrelevant parameters that we don't use (brightness etc.); + if (i % 8 == 0 && i > 0) s = s.concat("\n"); s = s.concat(String.format("0x%02x, ", cache[i])); diff --git a/src/aguegu/dotmatrix/DotMatrixTest.java b/src/aguegu/dotmatrix/DotMatrixTest.java index fbddf2a..4a6a252 100644 --- a/src/aguegu/dotmatrix/DotMatrixTest.java +++ b/src/aguegu/dotmatrix/DotMatrixTest.java @@ -7,7 +7,6 @@ import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.File; - import java.util.Arrays; import java.util.Locale; import java.util.ResourceBundle; @@ -26,7 +25,6 @@ import javax.swing.JScrollPane; import javax.swing.JToolBar; import javax.swing.ScrollPaneConstants; - import javax.swing.border.BevelBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; @@ -54,12 +52,14 @@ public class DotMatrixTest extends JFrame { private static final String PROGRAME_NAME = new String( "3D8 TF Animation Editor"); - private Locale locale = Locale.CHINESE; + private Locale locale = Locale.ENGLISH; private ResourceBundle res; private File fileRecord = null; private String message; private boolean isSaved = true; + private boolean firstInit = true; + private JFileChooser fs = new JFileChooser(); public static void main(String[] args) { DotMatrixTest dmt = new DotMatrixTest(); @@ -74,10 +74,9 @@ public void init() { this.getContentPane().removeAll(); - if (dmrf == null) - dmrf = new DMRecordFrame(0); + dmrf = new DMRecordFrame(0); - panelRecord = new DMRecordPanel(res); + panelRecord = new DMRecordPanel(this, panelRecord, res); panelRecord.setFrame(dmrf); panelToolbar = panelToolBar(); @@ -91,14 +90,10 @@ public void init() { .createBevelBorder(BevelBorder.LOWERED)); this.getContentPane().add(BorderLayout.SOUTH, labelStatus); - if (dmr == null) - dmr = new DMRecord(); + dmr = new DMRecord(); - if (listFrame == null) { - listFrame = new DMRecordList(dmr); - listFrame - .addListSelectionListener(new ListSelectionListenerListFrame()); - } + listFrame = new DMRecordList(dmr); + listFrame.addListSelectionListener(new ListSelectionListenerListFrame()); JScrollPane listFramePane = new JScrollPane(listFrame, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, @@ -114,7 +109,10 @@ public void init() { begin(); refreshFrame(); - this.setLocation(100, 100); + if (firstInit) { + firstInit = false; + this.setLocation(100, 100); + } this.pack(); this.setResizable(false); @@ -124,7 +122,6 @@ public void init() { private class ActionListenerFileOperation implements ActionListener { @Override public void actionPerformed(ActionEvent e) { - JFileChooser fs = new JFileChooser(); FileNameExtensionFilter filter = new FileNameExtensionFilter( "3D8 animation record (*.dat)", "dat"); fs.setFileFilter(filter); @@ -141,13 +138,16 @@ public void actionPerformed(ActionEvent e) { if (result == JOptionPane.YES_OPTION) save(); } - begin(); + init(); //begin break; case "open": result = fs.showOpenDialog(null); file = fs.getSelectedFile(); + if (file == null || result != JFileChooser.APPROVE_OPTION) break; + + fs.setCurrentDirectory(file); // save old directory fileRecord = file; dmr.readRecord(fileRecord); @@ -192,7 +192,7 @@ public void actionPerformed(ActionEvent e) { } } - private class ActionListenerRecordOperation implements ActionListener { + private class ActionListenerRocordOperation implements ActionListener { @Override public void actionPerformed(ActionEvent e) { int index = listFrame.getSelectedIndex(); @@ -242,7 +242,8 @@ public void actionPerformed(ActionEvent e) { String sSpan = JOptionPane.showInputDialog( res.getString("span_prompt"), "0x0080"); - if (sSpan != null && sSpan.matches("0[x|X][\\p{XDigit}]{4}")) { + if (sSpan != null + && sSpan.matches("0[x|X][\\p{XDigit}]{4}")) { int span = Integer.decode(sSpan); dmr.setSpan(span); } @@ -266,12 +267,20 @@ public void valueChanged(ListSelectionEvent e) { if (index == -1) return; - dmrf = dmr.getFrame(index); - panelRecord.setFrame(dmrf); - panelRecord.refresh(true); - refreshFrame(); + setActiveFrame(index); } } + + public void setActiveFrame(int index) { + dmrf = dmr.getFrame(index); + panelRecord.setFrame(dmrf); + panelRecord.refresh(true); + refreshFrame(); + } + + public int getNumFrames() { + return listFrame.getNumFrames(); + } private class DotMatrixTestMenuBar extends JMenuBar implements ActionListener { @@ -302,7 +311,7 @@ public DotMatrixTestMenuBar() { JMenuItem button = new JMenuItem(res.getString(s)); button.setActionCommand(s); - button.addActionListener(new ActionListenerRecordOperation()); + button.addActionListener(new ActionListenerRocordOperation()); mnRecord.add(button); } @@ -338,7 +347,7 @@ public void actionPerformed(ActionEvent e) { switch (e.getActionCommand()) { case "about": JOptionPane.showMessageDialog(this, - "For more info, check\nhttp://aguegu.net", + "For more info, check\nhttp://aguegu.net\nhttp://www.wzona.info", res.getString("about"), JOptionPane.OK_OPTION | JOptionPane.INFORMATION_MESSAGE); break; @@ -431,7 +440,7 @@ private JPanel panelToolBar() { JButton button = new JButton(new ImageIcon(getClass().getResource( "/image/" + s + ".png"))); button.setActionCommand(s); - button.addActionListener(new ActionListenerRecordOperation()); + button.addActionListener(new ActionListenerRocordOperation()); button.setToolTipText(res.getString(s)); toolbarReord.add(button); } diff --git a/src/aguegu/dotmatrix/DotMatrixTest.properties b/src/aguegu/dotmatrix/DotMatrixTest.properties index cd5fbb4..4130e67 100644 --- a/src/aguegu/dotmatrix/DotMatrixTest.properties +++ b/src/aguegu/dotmatrix/DotMatrixTest.properties @@ -9,10 +9,10 @@ frame=Frame record=Record help=Help -append=Append -insert=Insert -update=Update -delete=Delete +append=Append Frame to End +insert=Insert Frame Above Current +update=Update current frame +delete=Delete Current Frame language=Language english=English @@ -25,7 +25,7 @@ span=Span mode_prompt=Mode\ for\ All\ Frames:\ (0-2) brightness_prompt=Brightness\ for\ All\ Frames:\ (0x00-0xff)" span_prompt=Span\ for\ All\ Frames:\ (0x0000-0xffff) -message=Developed\ by\ http://aGuegu.net +message=Developed\ by\ http://aGuegu.net | Updates by http://www.wzona.info time_span=Time\ Span upper_led=Upper\ Led @@ -33,15 +33,15 @@ bottom_led=Bottom\ Led about=About -loop=Loop -on=All\ On -off=All\ Off -x+=X\ + -x-=X\ - -y+=Y\ + -y-=Y\ - -z+=Z\ + -z-=Z\ - +loop=Wrap +on=All LEDs On +off=All LEDs Off +x+=Shift X forward +x-=Shift X backwards +y+=Shift Y forward +y-=Shift X backwards +z+=Shift Z forward +z-=Shift Z backwards 3c=R3\ Clockwise 3a=R3\ Anticlockwise 2c=R2\ Clockwise @@ -50,7 +50,19 @@ z-=Z\ - 1a=R1\ Anticlockwise 0c=R0\ Clockwise 0a=R0\ Anticlockwise -xf=flip\ X -yf=flip\ Y -zf=flip\ Z -r=Reverse +xf=Flip X +yf=Flip Y +zf=Flip Z +r=Invert All LEDs + +baud=Baud +play=Play +stop=Stop +comport=Serial port +open_serial=Open +close_serial=Close +delay=Update delay(ms) +delay_ms=20 +refresh=Refresh +anim_loop=Loop animation +