/******************************************************************************* * Copyright (c) 2001, 2010 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 * * Contributors: * Mathew A. Nelson * - Initial API and implementation * Matthew Reeder * - Changes to facilitate Undo/Redo * - Support for line numbers * - Window menu * - Launching the Replace dialog using ctrl+H * Flemming N. Larsen * - Code cleanup * - Updated to use methods from the Logger, which replaces logger methods * that have been (re)moved from the robocode.util.Utils class *******************************************************************************/ package net.sf.robocode.ui.editor; import net.sf.robocode.io.Logger; import net.sf.robocode.repository.IRepositoryManager; import javax.swing.*; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; import javax.swing.filechooser.FileFilter; import java.awt.*; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.StringTokenizer; /** * @author Mathew A. Nelson (original) * @author Matthew Reeder (contributor) * @author Flemming N. Larsen (contributor) */ @SuppressWarnings("serial") public class EditWindow extends JInternalFrame implements CaretListener { private String fileName; private String robotName; public boolean modified; private final RobocodeEditor editor; private final IRepositoryManager repositoryManager; private JEditorPane editorPane; private JPanel editWindowContentPane; private final File robotsDirectory; private JScrollPane scrollPane; private LineNumbers lineNumbers; public EditWindow(IRepositoryManager repositoryManager, RobocodeEditor editor, File robotsDirectory) { super(); this.editor = editor; this.robotsDirectory = robotsDirectory; this.repositoryManager = repositoryManager; initialize(); } public JEditorPane getEditorPane() { if (editorPane == null) { editorPane = new JEditorPane(); editorPane.setFont(new Font("monospaced", 0, 12)); RobocodeEditorKit editorKit = new RobocodeEditorKit(); editorPane.setEditorKitForContentType("text/java", editorKit); editorPane.setContentType("text/java"); editorKit.setEditWindow(this); editorPane.addCaretListener(this); ((JavaDocument) editorPane.getDocument()).setEditWindow(this); InputMap im = editorPane.getInputMap(); // read: hack. im.put(KeyStroke.getKeyStroke("ctrl H"), editor.getReplaceAction()); // FIXME: Replace hack with better solution? } return editorPane; } public String getFileName() { return fileName; } public String getRobotName() { return robotName; } private void initialize() { try { this.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); } }); setResizable(true); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); setIconifiable(true); setClosable(true); setMaximum(false); setFrameIcon(new ImageIcon(EditWindow.class.getResource("/net/sf/robocode/ui/icons/robocode-icon.png"))); setSize(553, 441); setMaximizable(true); setTitle("Edit Window"); setContentPane(getEditWindowContentPane()); editor.addToWindowMenu(this); } catch (Throwable e) { Logger.logError(e); } } public void setFileName(String newFileName) { fileName = newFileName; } public void setModified(boolean modified) { if (modified && !this.modified) { this.modified = true; if (fileName != null) { setTitle("Editing - " + fileName + " *"); } else if (robotName != null) { setTitle("Editing - " + robotName + " *"); } else { setTitle("Editing - *"); } } else if (!modified) { this.modified = false; if (fileName != null) { setTitle("Editing - " + fileName); } else if (robotName != null) { setTitle("Editing - " + robotName); } else { setTitle("Editing"); } } editor.setSaveFileMenuItemsEnabled(modified); } public void setRobotName(String newRobotName) { robotName = newRobotName; } public void caretUpdate(CaretEvent e) { int lineend = getEditorPane().getDocument().getDefaultRootElement().getElementIndex(e.getDot()); editor.setLineStatus(lineend); } 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() { editor.getCompiler().compile(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); } } FileWriter writer = null; try { writer = new FileWriter(new File(fileName)); getEditorPane().write(writer); setModified(false); } catch (IOException e) { error("Cannot write file: " + e); return false; } finally { if (writer != null) { try { writer.close(); } catch (IOException ignored) {} } } return true; } public boolean fileSaveAs() { String javaFileName = null; String packageTree; String fileName = robotsDirectory.getPath() + File.separatorChar; String saveDir = fileName; String text = getEditorPane().getText(); int pIndex = text.indexOf("package "); if (pIndex >= 0) { int pEIndex = text.indexOf(";", pIndex); if (pEIndex > 0) { packageTree = text.substring(pIndex + 8, pEIndex) + File.separatorChar; packageTree = packageTree.replace('.', File.separatorChar); fileName += packageTree; saveDir = fileName; } } 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; } private JPanel getEditWindowContentPane() { if (editWindowContentPane == null) { editWindowContentPane = new JPanel(); editWindowContentPane.setLayout(new BorderLayout()); editWindowContentPane.setDoubleBuffered(true); editWindowContentPane.add(getScrollPane(), "Center"); } return editWindowContentPane; } 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; } private JScrollPane getScrollPane() { if (scrollPane == null) { scrollPane = new JScrollPane(); scrollPane.setViewportView(getEditorPane()); scrollPane.setRowHeaderView(getLineNumbers()); } return scrollPane; } private LineNumbers getLineNumbers() { if (lineNumbers == null) { lineNumbers = new LineNumbers(getEditorPane()); } return lineNumbers; } public void undo() { ((JavaDocument) getEditorPane().getDocument()).undo(); repaint(); } public void redo() { ((JavaDocument) getEditorPane().getDocument()).redo(); repaint(); } }