/** * Copyright (c) 2001-2017 Mathew A. Nelson and Robocode contributors * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://robocode.sourceforge.net/license/epl-v10.html */ package net.sf.robocode.ui.editor; import net.sf.robocode.io.FileUtil; import net.sf.robocode.io.Logger; import net.sf.robocode.repository.IRepositoryManager; import net.sf.robocode.ui.editor.theme.EditorThemeProperties; import net.sf.robocode.ui.editor.theme.EditorThemePropertiesManager; import net.sf.robocode.ui.editor.theme.EditorThemePropertyChangeAdapter; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; import javax.swing.filechooser.FileFilter; import java.awt.Font; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.StringTokenizer; /** * @author Mathew A. Nelson (original) * @author Flemming N. Larsen (contributor) * @author Matthew Reeder (contributor) */ @SuppressWarnings("serial") public class EditWindow extends JInternalFrame { private String fileName; private String robotName; public boolean modified; private final RobocodeEditor editor; private final IRepositoryManager repositoryManager; private final File robotsDirectory; private EditorPanel editorPanel; private EditorPane editorPane; public EditWindow(IRepositoryManager repositoryManager, RobocodeEditor editor, File robotsDirectory) { super("Edit Window", true, true, true, true); this.editor = editor; this.robotsDirectory = robotsDirectory; this.repositoryManager = repositoryManager; initialize(); } public EditorPane getEditorPane() { if (editorPane == null) { editorPane = editorPanel.getEditorPane(); InputMap im = editorPane.getInputMap(); // FIXME: Replace hack with better solution than using 'ctrl H' im.put(KeyStroke.getKeyStroke("ctrl H"), editor.getReplaceAction()); } return editorPane; } public String getFileName() { return fileName; } public String getRobotName() { return robotName; } private void initialize() { try { addInternalFrameListener(new InternalFrameAdapter() { @Override public void internalFrameClosing(InternalFrameEvent e) { if (!modified || fileSave(true)) { editor.setLineStatus(-1); dispose(); } editor.removeFromWindowMenu(EditWindow.this); } @Override public void internalFrameDeactivated(InternalFrameEvent e) { editor.setLineStatus(-1); } @Override public void internalFrameIconified(InternalFrameEvent e) { editor.setLineStatus(-1); } }); setFrameIcon(new ImageIcon(EditWindow.class.getResource("/net/sf/robocode/ui/icons/robocode-icon.png"))); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); setSize(750, 500); editor.addToWindowMenu(this); editorPanel = new EditorPanel(); setContentPane(editorPanel); EditorThemeProperties currentThemeProps = EditorThemePropertiesManager.getCurrentEditorThemeProperties(); Font font = currentThemeProps.getFont(); editorPanel.setFont(font); // Make sure the source editor window gets focus with a blinking cursor SwingUtilities.invokeLater(new Runnable() { @Override public void run() { editorPanel.getEditorPane().requestFocus(); } }); EditorThemePropertiesManager.addListener(new EditorThemePropertyChangeAdapter() { @Override public void onFontChanged(Font newFont) { editorPanel.setFont(newFont); } }); final JavaDocument document = (JavaDocument) editorPanel.getEditorPane().getDocument(); document.addDocumentListener(new DocumentListener() { public void removeUpdate(DocumentEvent e) { updateModificationState(); } public void insertUpdate(DocumentEvent e) { updateModificationState(); } public void changedUpdate(DocumentEvent e) { updateModificationState(); } // Bug-361 Problem in the text editor related with the .java file modification private void updateModificationState() { setModified(editorPanel.getEditorPane().isModified()); } }); } catch (Throwable e) { Logger.logError(e); } } public void setFileName(String newFileName) { fileName = newFileName; updateTitle(); } public void setRobotName(String newRobotName) { robotName = newRobotName; updateTitle(); } private void updateTitle() { StringBuffer titleBuf = new StringBuffer("Editing"); if (fileName != null) { titleBuf.append(" - ").append(fileName); } else if (robotName != null) { titleBuf.append(" - ").append(robotName); } if (modified) { titleBuf.append(" *"); } setTitle(titleBuf.toString()); } private void setModified(boolean modified) { boolean updated = (modified != this.modified); if (updated) { this.modified = modified; updateTitle(); editor.setSaveFileMenuItemsEnabled(modified); } } public void compile() { if (!fileSave(true, true)) { error("You must save before compiling."); return; } if (editor.getCompiler() != null) { // The compiler + refresh of the repository is done in a thread in order to avoid the compiler // window hanging while compiling. The SwingUtilities.invokeLater() does not do a good job here // (window is still hanging). Hence, a real thread running beside the EDT is used, which does a // great job, where each each new print from the compiler is written out as soon as it is ready // in the output stream. new Thread(new Runnable() { public void run() { if (fileName == null) { error("You must save before compiling."); return; } editor.getCompiler().compile(getRobotDir(), fileName); repositoryManager.refresh(fileName); } }).start(); } else { JOptionPane.showMessageDialog(editor, "No compiler installed.", "Error", JOptionPane.ERROR_MESSAGE); } } private void error(String msg) { Object[] options = { "OK" }; JOptionPane.showOptionDialog(this, msg, "Error", JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE, null, options, options[0]); } public boolean fileSave(boolean confirm) { return fileSave(confirm, false); } private boolean fileSave(boolean confirm, boolean mustSave) { if (confirm) { if (!modified) { return true; } String s = fileName; if (s == null) { s = robotName; } if (s == null) { s = "This file"; } int ok = JOptionPane.showConfirmDialog(this, s + " has been modified. Do you wish to save it?", "Modified file", JOptionPane.YES_NO_CANCEL_OPTION); if (ok == JOptionPane.NO_OPTION) { return !mustSave; } if (ok == JOptionPane.CANCEL_OPTION) { return false; } } String fileName = getFileName(); if (fileName == null) { return fileSaveAs(); } String reasonableFilename = getReasonableFilename(); if (reasonableFilename != null) { try { String a = new File(reasonableFilename).getCanonicalPath(); String b = new File(fileName).getCanonicalPath(); if (!a.equals(b)) { int ok = JOptionPane.showConfirmDialog(this, fileName + " should be saved in: \n" + reasonableFilename + "\n Would you like to save it there instead?", "Name has changed", JOptionPane.YES_NO_CANCEL_OPTION); if (ok == JOptionPane.CANCEL_OPTION) { return false; } if (ok == JOptionPane.YES_OPTION) { return fileSaveAs(); } } } catch (IOException e) { Logger.logError("Unable to check reasonable filename: ", e); } } BufferedWriter bufferedWriter = null; OutputStreamWriter outputStreamWriter = null; FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(fileName); outputStreamWriter = new OutputStreamWriter(fileOutputStream, "UTF8"); bufferedWriter = new BufferedWriter(outputStreamWriter); getEditorPane().write(bufferedWriter); setModified(false); } catch (IOException e) { error("Cannot write file: " + e); return false; } finally { FileUtil.cleanupStream(bufferedWriter); } return true; } private String getRobotDir() { String saveDir = robotsDirectory.getPath() + File.separatorChar; String text = getEditorPane().getText(); int pIndex = text.indexOf("package "); if (pIndex >= 0) { int pEIndex = text.indexOf(";", pIndex); if (pEIndex > 0) { String packageTree = text.substring(pIndex + 8, pEIndex) + File.separatorChar; packageTree = packageTree.replace('.', File.separatorChar); saveDir += packageTree; } } return saveDir; } public boolean fileSaveAs() { String javaFileName = null; String saveDir = getRobotDir(); String text = getEditorPane().getText(); int pIndex = text.indexOf("public class "); if (pIndex >= 0) { int pEIndex = text.indexOf(" ", pIndex + 13); if (pEIndex > 0) { int pEIndex2 = text.indexOf("\n", pIndex + 13); if (pEIndex2 > 0 && pEIndex2 < pEIndex) { pEIndex = pEIndex2; } javaFileName = text.substring(pIndex + 13, pEIndex).trim() + ".java"; } else { pEIndex = text.indexOf("\n", pIndex + 13); if (pEIndex > 0) { javaFileName = text.substring(pIndex + 13, pEIndex).trim() + ".java"; } } } File f = new File(saveDir); if (!f.exists()) { int ok = JOptionPane.showConfirmDialog(this, "Your robot should be saved in the directory: " + saveDir + "\nThis directory does not exist, would you like to create it?", "Create Directory", JOptionPane.YES_NO_CANCEL_OPTION); if (ok == JOptionPane.YES_OPTION) { if (!f.exists() && !f.mkdirs()) { Logger.logError("Cannot create: " + f); } f = new File(saveDir); } if (ok == JOptionPane.CANCEL_OPTION) { return false; } } JFileChooser chooser; chooser = new JFileChooser(f); chooser.setCurrentDirectory(f); FileFilter filter = new FileFilter() { @Override public boolean accept(File pathname) { if (pathname.isDirectory()) { return true; } String fn = pathname.getName(); int idx = fn.lastIndexOf('.'); String extension = ""; if (idx >= 0) { extension = fn.substring(idx); } return extension.equalsIgnoreCase(".java"); } @Override public String getDescription() { return "Robots"; } }; chooser.setFileFilter(filter); boolean done = false; while (!done) { done = true; if (javaFileName != null) { chooser.setSelectedFile(new File(f, javaFileName)); } int rv = chooser.showSaveDialog(this); String robotFileName; if (rv == JFileChooser.APPROVE_OPTION) { robotFileName = chooser.getSelectedFile().getPath(); File outFile = new File(robotFileName); if (outFile.exists()) { int ok = JOptionPane.showConfirmDialog(this, robotFileName + " already exists. Are you sure you want to replace it?", "Warning", JOptionPane.YES_NO_CANCEL_OPTION); if (ok == JOptionPane.NO_OPTION) { done = false; continue; } if (ok == JOptionPane.CANCEL_OPTION) { return false; } } setFileName(robotFileName); fileSave(false); } else { return false; } } return true; } public String getPackage() { String text = getEditorPane().getText(); int pIndex = text.indexOf("package "); if (pIndex >= 0) { int pEIndex = text.indexOf(";", pIndex); if (pEIndex > 0) { return text.substring(pIndex + 8, pEIndex); } } return ""; } private String getReasonableFilename() { StringBuffer fileName = new StringBuffer(robotsDirectory.getPath()).append(File.separatorChar); String javaFileName; String packageTree = null; String text = getEditorPane().getText(); StringTokenizer tokenizer = new StringTokenizer(text, " \t\r\n;"); String token; boolean inComment = false; while (tokenizer.hasMoreTokens()) { token = tokenizer.nextToken(); if (!inComment && (token.equals("/*") || token.equals("/**"))) { inComment = true; } if (inComment && (token.equals("*/") || token.equals("**/"))) { inComment = false; } if (inComment) { continue; } if (packageTree == null && token.equals("package")) { packageTree = tokenizer.nextToken(); if (packageTree == null || packageTree.length() == 0) { return null; } packageTree = packageTree.replace('.', File.separatorChar); packageTree += File.separator; fileName.append(packageTree); } if (token.equals("class")) { javaFileName = tokenizer.nextToken() + ".java"; fileName.append(javaFileName); return fileName.toString(); } } return null; } }