/* * RomRaider Open-Source Tuning, Logging and Reflashing * Copyright (C) 2006-2014 RomRaider.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package com.romraider.editor.ecu; import static com.romraider.Version.ECU_DEFS_URL; import static com.romraider.Version.PRODUCT_NAME; import static com.romraider.Version.VERSION; import static javax.swing.JOptionPane.DEFAULT_OPTION; import static javax.swing.JOptionPane.ERROR_MESSAGE; import static javax.swing.JOptionPane.INFORMATION_MESSAGE; import static javax.swing.JOptionPane.WARNING_MESSAGE; import static javax.swing.JOptionPane.showMessageDialog; import static javax.swing.JOptionPane.showOptionDialog; import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED; import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER; import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS; import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyVetoException; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.util.Vector; import javax.swing.ImageIcon; import javax.swing.JCheckBox; import javax.swing.JInternalFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.SwingWorker; import javax.swing.tree.TreePath; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXParseException; import com.romraider.Settings; import com.romraider.logger.ecu.EcuLogger; import com.romraider.maps.Rom; import com.romraider.net.BrowserControl; import com.romraider.net.URL; import com.romraider.swing.AbstractFrame; import com.romraider.swing.CustomToolbarLayout; import com.romraider.swing.ECUEditorMenuBar; import com.romraider.swing.ECUEditorToolBar; import com.romraider.swing.JProgressPane; import com.romraider.swing.MDIDesktopPane; import com.romraider.swing.RomTree; import com.romraider.swing.RomTreeRootNode; import com.romraider.swing.TableFrame; import com.romraider.swing.TableToolBar; import com.romraider.util.SettingsManager; import com.romraider.xml.DOMRomUnmarshaller; import com.romraider.xml.RomNotFoundException; import com.sun.org.apache.xerces.internal.parsers.DOMParser; public class ECUEditor extends AbstractFrame { private static final long serialVersionUID = -7826850987392016292L; private final String titleText = PRODUCT_NAME + " v" + VERSION + " | ECU Editor"; private final RomTreeRootNode imageRoot = new RomTreeRootNode("Open Images"); private final RomTree imageList = new RomTree(imageRoot); private final MDIDesktopPane rightPanel = new MDIDesktopPane(); private final JProgressPane statusPanel = new JProgressPane(); private final JScrollPane leftScrollPane; private final JScrollPane rightScrollPane; private JSplitPane splitPane = new JSplitPane(); private Rom lastSelectedRom = null; private ECUEditorToolBar toolBar; private ECUEditorMenuBar menuBar; private TableToolBar tableToolBar; private final JPanel toolBarPanel = new JPanel(); private OpenImageWorker openImageWorker; private CloseImageWorker closeImageWorker; private SetUserLevelWorker setUserLevelWorker; private LaunchLoggerWorker launchLoggerWorker; private final ImageIcon editorIcon = new ImageIcon(getClass().getResource("/graphics/romraider-ico.gif"), "RomRaider ECU Editor"); public ECUEditor() { Settings settings = SettingsManager.getSettings(); if (!settings.getRecentVersion().equalsIgnoreCase(VERSION)) { showReleaseNotes(); } setSize(settings.getWindowSize()); setLocation(settings.getWindowLocation()); if (settings.isWindowMaximized()) { setExtendedState(MAXIMIZED_BOTH); } rightScrollPane = new JScrollPane(rightPanel, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED); leftScrollPane = new JScrollPane(imageList, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED); splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftScrollPane, rightScrollPane); splitPane.setDividerSize(3); splitPane.setDividerLocation(settings.getSplitPaneLocation()); splitPane.addPropertyChangeListener(this); splitPane.setContinuousLayout(true); getContentPane().add(splitPane); rightPanel.setBackground(Color.BLACK); imageList.setScrollsOnExpand(true); this.add(statusPanel, BorderLayout.SOUTH); //set remaining window properties setIconImage(editorIcon.getImage()); setDefaultCloseOperation(EXIT_ON_CLOSE); addWindowListener(this); setTitle(titleText); setVisible(true); toFront(); } public void initializeEditorUI() { //create menubar menuBar = new ECUEditorMenuBar(); this.setJMenuBar(menuBar); // create toolbars toolBar = new ECUEditorToolBar("Editor Tools"); tableToolBar = new TableToolBar(); tableToolBar.updateTableToolBar(); CustomToolbarLayout toolBarLayout = new CustomToolbarLayout(FlowLayout.LEFT, 0, 0); toolBarPanel.setLayout(toolBarLayout); toolBarPanel.add(toolBar); toolBarPanel.add(tableToolBar); toolBarPanel.setVisible(true); this.add(toolBarPanel, BorderLayout.NORTH); validate(); } public void checkDefinitions() { if (SettingsManager.getSettings().getEcuDefinitionFiles().size() <= 0) { // no ECU definitions configured - let user choose to get latest or configure later Object[] options = {"Yes", "No"}; int answer = showOptionDialog(null, "ECU definitions not configured.\nGo online to download the latest definition files?", "Editor Configuration", DEFAULT_OPTION, WARNING_MESSAGE, null, options, options[0]); if (answer == 0) { BrowserControl.displayURL(ECU_DEFS_URL); } else { showMessageDialog(this, "ECU definition files need to be configured before ROM images can be opened.\nMenu: ECU Definitions > ECU Definition Manager...", "Editor Configuration", INFORMATION_MESSAGE); } } } private void showReleaseNotes() { try { BufferedReader br = new BufferedReader(new FileReader(SettingsManager.getSettings().getReleaseNotes())); try { // new version being used, display release notes JTextArea releaseNotes = new JTextArea(); releaseNotes.setEditable(false); releaseNotes.setWrapStyleWord(true); releaseNotes.setLineWrap(true); releaseNotes.setFont(new Font("Tahoma", Font.PLAIN, 12)); StringBuffer sb = new StringBuffer(); while (br.ready()) { sb.append(br.readLine()).append(Settings.NEW_LINE); } releaseNotes.setText(sb.toString()); releaseNotes.setCaretPosition(0); JScrollPane scroller = new JScrollPane(releaseNotes, VERTICAL_SCROLLBAR_ALWAYS, HORIZONTAL_SCROLLBAR_NEVER); scroller.setPreferredSize(new Dimension(600, 500)); showMessageDialog(this, scroller, PRODUCT_NAME + VERSION + " Release Notes", INFORMATION_MESSAGE); } finally { br.close(); } } catch (Exception e) { /* Ignore */ } } public void handleExit() { Settings settings = SettingsManager.getSettings(); settings.setSplitPaneLocation(splitPane.getDividerLocation()); settings.setWindowMaximized(getExtendedState() == MAXIMIZED_BOTH); settings.setWindowSize(getSize()); settings.setWindowLocation(getLocation()); // Save when exit to save file settings. SettingsManager.save(settings, statusPanel); statusPanel.update("Ready...", 0); repaint(); } @Override public void windowClosing(WindowEvent e) { handleExit(); } @Override public void windowOpened(WindowEvent e) { } @Override public void windowClosed(WindowEvent e) { } @Override public void windowIconified(WindowEvent e) { } @Override public void windowDeiconified(WindowEvent e) { } @Override public void windowActivated(WindowEvent e) { } @Override public void windowDeactivated(WindowEvent e) { } public String getVersion() { return VERSION; } public void addRom(Rom input) { Settings settings = SettingsManager.getSettings(); input.refreshDisplayedTables(); // add to ecu image list pane getImageRoot().add(input); getImageList().setVisible(true); getImageList().expandPath(new TreePath(getImageRoot())); getImageList().expandPath(new TreePath(input.getPath())); if(!settings.isOpenExpanded()) { imageList.collapsePath(new TreePath(input.getPath())); } getImageList().setRootVisible(false); getImageList().repaint(); // Only set if no other rom has been selected. if(null == getLastSelectedRom()) { setLastSelectedRom(input); } if (input.getRomID().isObsolete() && settings.isObsoleteWarning()) { JPanel infoPanel = new JPanel(); infoPanel.setLayout(new GridLayout(3, 1)); infoPanel.add(new JLabel("A newer version of this ECU revision exists. " + "Please visit the following link to download the latest revision:")); infoPanel.add(new URL(settings.getRomRevisionURL())); JCheckBox check = new JCheckBox("Always display this message", true); check.setHorizontalAlignment(JCheckBox.RIGHT); check.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { SettingsManager.getSettings().setObsoleteWarning(((JCheckBox) e.getSource()).isSelected()); } }); infoPanel.add(check); showMessageDialog(this, infoPanel, "ECU Revision is Obsolete", INFORMATION_MESSAGE); } } public void displayTable(TableFrame frame) { try { // check if frame has been added. for(JInternalFrame curFrame : getRightPanel().getAllFrames()) { if(curFrame.equals(frame)) { // table is already open. if(1 == SettingsManager.getSettings().getTableClickBehavior()) { // open/focus frame // table is already open, so set focus on the frame. boolean selected = true; frame.toFront(); try { frame.setSelected(true); } catch (PropertyVetoException e) { frame.toBack(); selected = false; } if(selected) { frame.requestFocusInWindow(); } } else { // default to open/close frame // table is already open, so close the frame. rightPanel.remove(frame); frame.setVisible(false); try { frame.setClosed(true); } catch (PropertyVetoException e) { ; // Do nothing. } frame.dispose(); } frame.pack(); rightPanel.repaint(); return; } } // frame not added. Draw table and add the frame. frame.getTable().drawTable(); rightPanel.add(frame); } catch (IllegalArgumentException ex) { ;// Do nothing. } frame.pack(); rightPanel.repaint(); } public void removeDisplayTable(TableFrame frame) { frame.setVisible(false); this.getTableToolBar().updateTableToolBar(); rightPanel.remove(frame); rightPanel.validate(); refreshUI(); } public void closeImage() { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); closeImageWorker = new CloseImageWorker(getLastSelectedRom()); closeImageWorker.addPropertyChangeListener(getStatusPanel()); closeImageWorker.execute(); } public void closeAllImages() { while (imageRoot.getChildCount() > 0) { closeImage(); } } public Rom getLastSelectedRom() { return lastSelectedRom; } public String getLastSelectedRomFileName() { Rom lastSelRom = getLastSelectedRom(); return lastSelRom == null ? "" : lastSelRom.getFileName() + " "; } public void setLastSelectedRom(Rom lastSelectedRom) { this.lastSelectedRom = lastSelectedRom; if (lastSelectedRom == null) { setTitle(titleText); } else { setTitle(titleText + " - " + lastSelectedRom.getFileName()); } } public ECUEditorToolBar getToolBar() { return toolBar; } public void setToolBar(ECUEditorToolBar toolBar) { this.toolBar = toolBar; } public ECUEditorMenuBar getEditorMenuBar() { return menuBar; } public TableToolBar getTableToolBar() { return tableToolBar; } public void redrawVisableTables(Settings settings) { } public void setUserLevel(int userLevel) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); SettingsManager.getSettings().setUserLevel(userLevel); setUserLevelWorker = new SetUserLevelWorker(); setUserLevelWorker.addPropertyChangeListener(getStatusPanel()); setUserLevelWorker.execute(); } public Vector<Rom> getImages() { Vector<Rom> images = new Vector<Rom>(); for (int i = 0; i < imageRoot.getChildCount(); i++) { if(imageRoot.getChildAt(i) instanceof Rom) { Rom rom = (Rom) imageRoot.getChildAt(i); if(null != rom) { images.add(rom); } } } return images; } @Override public void propertyChange(PropertyChangeEvent evt) { imageList.updateUI(); imageList.repaint(); rightPanel.updateUI(); rightPanel.repaint(); } public void refreshUI() { getToolBar().updateButtons(); getEditorMenuBar().updateMenu(); getTableToolBar().updateTableToolBar(); imageList.updateUI(); imageList.repaint(); rightPanel.updateUI(); rightPanel.repaint(); } public void refreshTableCompareMenus() { for (int i = 0; i < imageRoot.getChildCount(); i++) { if(imageRoot.getChildAt(i) instanceof Rom) { Rom rom = (Rom) imageRoot.getChildAt(i); if(null != rom) { rom.refreshTableCompareMenus(); } } } } public void openImage(File inputFile) throws Exception { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); openImageWorker = new OpenImageWorker(inputFile); openImageWorker.addPropertyChangeListener(getStatusPanel()); openImageWorker.execute(); } public void openImages(File[] inputFiles) throws Exception { if(inputFiles.length < 1) { showMessageDialog(this, "Image Not Found", "Error Loading Image(s)", ERROR_MESSAGE); return; } for(int j = 0; j < inputFiles.length; j++) { openImage(inputFiles[j]); } } public byte[] readFile(File inputFile) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); FileInputStream fis = new FileInputStream(inputFile); try { byte[] buf = new byte[8192]; int bytesRead; while ((bytesRead = fis.read(buf)) != -1) { baos.write(buf, 0, bytesRead); } } finally { fis.close(); } return baos.toByteArray(); } public void launchLogger() { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); launchLoggerWorker = new LaunchLoggerWorker(); launchLoggerWorker.addPropertyChangeListener(getStatusPanel()); launchLoggerWorker.execute(); } public RomTreeRootNode getImageRoot() { return imageRoot; } public RomTree getImageList() { return imageList; } public JProgressPane getStatusPanel() { return this.statusPanel; } public MDIDesktopPane getRightPanel() { return this.rightPanel; } public JScrollPane getLeftScrollPane() { return this.leftScrollPane; } public JScrollPane getRightScrollPane() { return this.rightScrollPane; } } class LaunchLoggerWorker extends SwingWorker<Void, Void> { public LaunchLoggerWorker() { } @Override protected Void doInBackground() throws Exception { ECUEditor editor = ECUEditorManager.getECUEditor(); editor.getStatusPanel().setStatus("Launching Logger..."); setProgress(10); EcuLogger.startLogger(javax.swing.WindowConstants.DISPOSE_ON_CLOSE, editor); return null; } public void propertyChange(PropertyChangeEvent evnt) { SwingWorker source = (SwingWorker) evnt.getSource(); if (null != source && "state".equals( evnt.getPropertyName() ) && (source.isDone() || source.isCancelled() ) ) { source.removePropertyChangeListener(ECUEditorManager.getECUEditor().getStatusPanel()); } } @Override public void done() { ECUEditor editor = ECUEditorManager.getECUEditor(); editor.getStatusPanel().setStatus("Ready..."); setProgress(0); editor.setCursor(null); editor.refreshUI(); } } class SetUserLevelWorker extends SwingWorker<Void, Void> { public SetUserLevelWorker() { } @Override protected Void doInBackground() throws Exception { for(Rom rom : ECUEditorManager.getECUEditor().getImages()) { rom.refreshDisplayedTables(); } return null; } public void propertyChange(PropertyChangeEvent evnt) { SwingWorker source = (SwingWorker) evnt.getSource(); if (null != source && "state".equals( evnt.getPropertyName() ) && (source.isDone() || source.isCancelled() ) ) { source.removePropertyChangeListener(ECUEditorManager.getECUEditor().getStatusPanel()); } } @Override public void done() { ECUEditor editor = ECUEditorManager.getECUEditor(); editor.getStatusPanel().setStatus("Ready..."); setProgress(0); editor.setCursor(null); editor.refreshUI(); } } class CloseImageWorker extends SwingWorker<Void, Void> { Rom rom; public CloseImageWorker(Rom romToRemove) { this.rom = romToRemove; } @Override protected Void doInBackground() throws Exception { ECUEditor editor = ECUEditorManager.getECUEditor(); RomTreeRootNode imageRoot = editor.getImageRoot(); rom.clearData(); rom.removeFromParent(); rom = null; if (imageRoot.getChildCount() > 0) { editor.setLastSelectedRom((Rom) imageRoot.getChildAt(0)); } else { // no other images open editor.setLastSelectedRom(null); } editor.refreshTableCompareMenus(); return null; } @Override public void done() { ECUEditor editor = ECUEditorManager.getECUEditor(); editor.getStatusPanel().setStatus("Ready..."); setProgress(0); editor.setCursor(null); editor.refreshUI(); System.gc(); } } class OpenImageWorker extends SwingWorker<Void, Void> { private final File inputFile; public OpenImageWorker(File inputFile) { this.inputFile = inputFile; } @Override protected Void doInBackground() throws Exception { ECUEditor editor = ECUEditorManager.getECUEditor(); Settings settings = SettingsManager.getSettings(); DOMParser parser = new DOMParser(); Document doc; FileInputStream fileStream; try { editor.getStatusPanel().setStatus("Parsing ECU definitions..."); setProgress(0); byte[] input = editor.readFile(inputFile); editor.getStatusPanel().setStatus("Finding ECU definition..."); setProgress(10); // parse ecu definition files until result found for (int i = 0; i < settings.getEcuDefinitionFiles().size(); i++) { if (!settings.getEcuDefinitionFiles().get(i).exists()) { showMessageDialog(editor, "ECU Definition file missing or moved. Please correct the ECU Definition Manager listing.\n" + settings.getEcuDefinitionFiles().get(i).getAbsolutePath(), "Missing ECU Definition File - " + settings.getEcuDefinitionFiles().get(i).getName(), ERROR_MESSAGE); continue; } fileStream = new FileInputStream(settings.getEcuDefinitionFiles().get(i)); InputSource src = new InputSource(fileStream); parser.parse(src); doc = parser.getDocument(); Rom rom; try { rom = new DOMRomUnmarshaller().unmarshallXMLDefinition(doc.getDocumentElement(), input, editor.getStatusPanel()); } catch (RomNotFoundException rex) { // rom was not found in current file, skip to next continue; } catch (Exception ex) { ex.printStackTrace(); showMessageDialog(editor, "Error Loading. Unknown Exception.", "Error Loading " + inputFile.getName(), ERROR_MESSAGE); return null; } finally { // Release mem after unmarshall. parser.reset(); doc.removeChild(doc.getDocumentElement()); doc = null; fileStream.close(); System.gc(); } editor.getStatusPanel().setStatus("Populating tables..."); setProgress(50); rom.setFullFileName(inputFile); rom.populateTables(input, editor.getStatusPanel()); editor.getStatusPanel().setStatus("Finalizing..."); setProgress(90); editor.addRom(rom); editor.refreshTableCompareMenus(); editor.getStatusPanel().setStatus("Done loading image..."); setProgress(100); return null; } // if code executes to this point, no ROM was found, report to user showMessageDialog(editor, "ECU Definition Not Found", "Error Loading " + inputFile.getName(), ERROR_MESSAGE); } catch (SAXParseException spe) { // catch general parsing exception - enough people don't unzip the defs that a better error message is in order showMessageDialog(editor, "Unable to read XML definitions. Please make sure the definition file is correct. If it is in a ZIP archive, unzip the file and try again.", "Error Loading " + inputFile.getName(), ERROR_MESSAGE); } catch (StackOverflowError ex) { // handles looped inheritance, which will use up all available memory showMessageDialog(editor, "Looped \"base\" attribute in XML definitions.", "Error Loading " + inputFile.getName(), ERROR_MESSAGE); } catch (OutOfMemoryError ome) { // handles Java heap space issues when loading multiple Roms. showMessageDialog(editor, "Error loading Image. Out of memeory.", "Error Loading " + inputFile.getName(), ERROR_MESSAGE); } catch (Exception ex) { ex.printStackTrace(); showMessageDialog(editor, "Error Loading. Caught Exception:\n" + ex.getMessage(), "Error Loading " + inputFile.getName(), ERROR_MESSAGE); } return null; } public void propertyChange(PropertyChangeEvent evnt) { SwingWorker source = (SwingWorker) evnt.getSource(); if (null != source && "state".equals( evnt.getPropertyName() ) && (source.isDone() || source.isCancelled() ) ) { source.removePropertyChangeListener(ECUEditorManager.getECUEditor().getStatusPanel()); } } @Override public void done() { ECUEditor editor = ECUEditorManager.getECUEditor(); editor.getStatusPanel().setStatus("Ready..."); setProgress(0); editor.setCursor(null); editor.refreshUI(); System.gc(); } }