/*******************************************************************************
* 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 for Find/Replace commands and Window menu
* Flemming N. Larsen
* - Code cleanup
* - Bugfixed the removeFromWindowMenu() method which did not remove the
* correct item, and did not break out of the loop when it was found.
* - Updated to use methods from ImageUtil, FileUtil, Logger, which replaces
* methods that have been (re)moved from the robocode.util.Utils class
* - Changed to use FileUtil.getRobocodeConfigFile() and
* FileUtil.getRobotsDir()
* - Added missing close() on FileInputStream, FileOutputStream, and
* FileReader
*******************************************************************************/
package net.sf.robocode.ui.editor;
import net.sf.robocode.core.Container;
import net.sf.robocode.io.FileUtil;
import net.sf.robocode.io.Logger;
import net.sf.robocode.repository.IRepositoryManager;
import net.sf.robocode.ui.BrowserManager;
import net.sf.robocode.ui.IWindowManager;
import net.sf.robocode.ui.IWindowManagerExt;
import net.sf.robocode.ui.gfx.ImageUtil;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.Document;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
/**
* @author Mathew A. Nelson (original)
* @author Matthew Reeder (contributor)
* @author Flemming N. Larsen (contributor)
*/
@SuppressWarnings("serial")
public class RobocodeEditor extends JFrame implements Runnable, IRobocodeEditor {
private static final int MAX_PACKAGE_NAME_LENGTH = 16;
private static final int MAX_ROBOT_NAME_LENGTH = 32;
private JPanel robocodeEditorContentPane;
private RobocodeEditorMenuBar robocodeEditorMenuBar;
private JDesktopPane desktopPane;
public boolean isApplication;
public final Point origin = new Point();
public final File robotsDirectory;
private JToolBar statusBar;
private JLabel lineLabel;
private File editorDirectory;
private final IRepositoryManager repositoryManager;
private final IWindowManagerExt windowManager;
private FindReplaceDialog findReplaceDialog;
private ReplaceAction replaceAction;
final EventHandler eventHandler = new EventHandler();
class EventHandler implements ComponentListener {
public void componentMoved(ComponentEvent e) {}
public void componentHidden(ComponentEvent e) {}
public void componentShown(ComponentEvent e) {
new Thread(RobocodeEditor.this).start();
}
public void componentResized(ComponentEvent e) {}
}
/**
* Action that launches the Replace dialog.
* <p/>
* The reason this is needed (and the menubar isn't sufficient) is that
* ctrl+H is bound in JTextComponents at a lower level to backspace and in
* order to override this, I need to rebind it to an Action when the
* JEditorPane is created.
*/
class ReplaceAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
replaceDialog();
}
}
public RobocodeEditor(IRepositoryManager repositoryManager, IWindowManager windowManager) {
super();
this.windowManager = (IWindowManagerExt) windowManager;
this.repositoryManager = repositoryManager;
robotsDirectory = FileUtil.getRobotsDir();
initialize();
}
public void addPlaceShowFocus(JInternalFrame internalFrame) {
getDesktopPane().add(internalFrame);
// Center a window
Dimension screenSize = getDesktopPane().getSize();
Dimension size = internalFrame.getSize();
if (size.height > screenSize.height) {
size.height = screenSize.height;
}
if (size.width > screenSize.width) {
size.width = screenSize.width;
}
if (origin.x + size.width > screenSize.width) {
origin.x = 0;
}
if (origin.y + size.height > screenSize.height) {
origin.y = 0;
}
internalFrame.setLocation(origin);
origin.x += 10;
origin.y += 10;
internalFrame.setVisible(true);
getDesktopPane().moveToFront(internalFrame);
if (internalFrame instanceof EditWindow) {
((EditWindow) internalFrame).getEditorPane().requestFocus();
} else {
internalFrame.requestFocus();
}
}
public boolean close() {
JInternalFrame[] frames = getDesktopPane().getAllFrames();
if (frames != null) {
for (JInternalFrame frame : frames) {
if (frame != null) {
frame.moveToFront();
if ((frame instanceof EditWindow) && !((EditWindow) frame).fileSave(true)) {
return false;
}
}
}
}
if (isApplication) {
System.exit(0);
} else {
dispose();
}
return true;
}
public void createNewJavaFile() {
String packageName = null;
if (getActiveWindow() != null) {
packageName = getActiveWindow().getPackage();
}
if (packageName == null) {
packageName = "mypackage";
}
EditWindow editWindow = new EditWindow(repositoryManager, this, robotsDirectory);
editWindow.setModified(false);
String templateName = "templates" + File.separatorChar + "newjavafile.tpt";
String template = "";
File f = new File(FileUtil.getCwd(), templateName);
int size = (int) (f.length());
byte buff[] = new byte[size];
FileInputStream fis = null;
DataInputStream dis = null;
try {
fis = new FileInputStream(f);
dis = new DataInputStream(fis);
dis.readFully(buff);
template = new String(buff);
} catch (IOException e) {
template = "Unable to read template file: " + FileUtil.getCwd() + File.separatorChar + templateName;
} finally {
FileUtil.cleanupStream(fis);
FileUtil.cleanupStream(dis);
}
String name = "MyClass";
int index = template.indexOf("$");
while (index >= 0) {
if (template.substring(index, index + 10).equals("$CLASSNAME")) {
template = template.substring(0, index) + name + template.substring(index + 10);
index += name.length();
} else if (template.substring(index, index + 8).equals("$PACKAGE")) {
template = template.substring(0, index) + packageName + template.substring(index + 8);
index += packageName.length();
} else {
index++;
}
index = template.indexOf("$", index);
}
editWindow.getEditorPane().setText(template);
editWindow.getEditorPane().setCaretPosition(0);
Document d = editWindow.getEditorPane().getDocument();
if (d instanceof JavaDocument) {
((JavaDocument) d).setEditing(true);
}
addPlaceShowFocus(editWindow);
}
public void createNewRobot() {
createNewRobot("Robot");
}
public void createNewJuniorRobot() {
createNewRobot("JuniorRobot");
}
private void createNewRobot(final String robotType) {
String message = "Enter the name of your new robot.\nExample: MyFirst" + robotType;
String name = "";
boolean done = false;
while (!done) {
name = (String) JOptionPane.showInputDialog(this, message, "New " + robotType, JOptionPane.PLAIN_MESSAGE,
null, null, name);
if (name == null) {
return;
}
name = name.trim();
if (name.length() == 0) {
return;
}
if (name.length() > MAX_ROBOT_NAME_LENGTH) {
name = name.substring(0, MAX_ROBOT_NAME_LENGTH);
message = "Please choose a shorter name (" + MAX_ROBOT_NAME_LENGTH + " characters or less)";
continue;
}
final char firstLetter = name.charAt(0);
if (!Character.isJavaIdentifierStart(firstLetter) || Character.isLowerCase(firstLetter)
|| firstLetter == '$') {
name = name.substring(0, 1).toUpperCase() + name.substring(1);
message = "Please start your robot name with a big letter (A-Z),\nas should the first letter of every word in the name.\nExample: MyFirstRobot";
continue;
}
done = true;
for (int i = 1; i < name.length(); i++) {
char ch = name.charAt(i);
if (!Character.isJavaIdentifierPart(ch) || ch == '$') {
done = false;
break;
}
}
if (!done) {
message = "Your robot name contains an invalid character.\nPlease use only letters, digits, combination marks and underscores";
continue;
}
}
message = "Enter a short package name for your new robot. Your initials will work well here.\n"
+ "Your robot will be put into this package to avoid name conflict with other robots.\n"
+ "The package name is used to identify your robot(s) in the game, especially if you\n"
+ "want to let your robot(s) participate in competitions like e.g. RoboRumble@Home.\n"
+ "Hence, you should enter the same package name for all of your robots.";
String packageName = "";
done = false;
while (!done) {
packageName = (String) JOptionPane.showInputDialog(this, message, "Package name for " + name,
JOptionPane.PLAIN_MESSAGE, null, null, packageName);
if (packageName == null) {
return;
}
packageName = packageName.trim();
if (packageName.length() == 0) {
return;
}
if (packageName.length() > MAX_PACKAGE_NAME_LENGTH) {
packageName = packageName.substring(0, MAX_PACKAGE_NAME_LENGTH);
message = "Please choose a shorter name (" + MAX_PACKAGE_NAME_LENGTH + " characters or less)";
continue;
}
final char firstLetter = packageName.charAt(0);
if (!Character.isJavaIdentifierStart(firstLetter) || firstLetter == '$') {
packageName = packageName.toLowerCase();
message = "Please start the package name with a small letter (a-z)";
continue;
}
done = true;
for (int i = 1; i < name.length(); i++) {
char ch = name.charAt(i);
if (!(Character.isJavaIdentifierPart(ch) || ch == '.') || ch == '$') {
done = false;
break;
}
}
if (!done) {
message = "Your pakage name contains an invalid character.\nPlease use only small letters, digits, combination marks and underscores";
continue;
}
done = false;
// Uncommented block, as some robots seems to use upper case names for their package names/initials
// if (!packageName.equals(packageName.toLowerCase())) {
// packageName = packageName.toLowerCase();
// message = "Please use all small letters here.";
// continue;
// }
if (packageName.charAt(packageName.length() - 1) == '.') {
message = "The package name cannot end with a dot";
continue;
}
boolean wrong_dot_combination = false;
int lastDotIndex = -1;
for (int i = 0; i < packageName.length(); i++) {
if (packageName.charAt(i) == '.') {
if (i - lastDotIndex == 1) {
wrong_dot_combination = true;
}
lastDotIndex = i;
break;
}
}
if (wrong_dot_combination) {
message = "The package name contain two dots next to each other";
continue;
}
if (repositoryManager != null) {
done = repositoryManager.verifyRobotName(packageName + "." + name, name);
}
if (!done) {
message = "This package is reserved. Please select a different package.";
continue;
}
}
EditWindow editWindow = new EditWindow(repositoryManager, this, robotsDirectory);
editWindow.setRobotName(name);
editWindow.setModified(false);
String templateName = "templates" + File.separatorChar + "new" + robotType.toLowerCase() + ".tpt";
String template = "";
File f = new File(FileUtil.getCwd(), templateName);
int size = (int) (f.length());
byte buff[] = new byte[size];
FileInputStream fis = null;
DataInputStream dis = null;
try {
fis = new FileInputStream(f);
dis = new DataInputStream(fis);
dis.readFully(buff);
template = new String(buff);
} catch (IOException e) {
template = "Unable to read template file: " + FileUtil.getCwd() + File.separatorChar + templateName;
} finally {
FileUtil.cleanupStream(fis);
FileUtil.cleanupStream(dis);
}
int index = template.indexOf("$");
while (index >= 0) {
if (template.substring(index, index + 10).equals("$CLASSNAME")) {
template = template.substring(0, index) + name + template.substring(index + 10);
index += name.length();
} else if (template.substring(index, index + 8).equals("$PACKAGE")) {
template = template.substring(0, index) + packageName + template.substring(index + 8);
index += packageName.length();
} else {
index++;
}
index = template.indexOf("$", index);
}
editWindow.getEditorPane().setText(template);
editWindow.getEditorPane().setCaretPosition(0);
Document d = editWindow.getEditorPane().getDocument();
if (d instanceof JavaDocument) {
((JavaDocument) d).setEditing(true);
}
addPlaceShowFocus(editWindow);
if (repositoryManager != null) {
repositoryManager.refresh();
}
}
public void findDialog() {
getFindReplaceDialog().showDialog(false);
}
public void replaceDialog() {
getFindReplaceDialog().showDialog(true);
}
public EditWindow getActiveWindow() {
JInternalFrame[] frames = getDesktopPane().getAllFrames();
EditWindow editWindow = null;
if (frames != null) {
for (JInternalFrame frame : frames) {
if (frame.isSelected()) {
if (frame instanceof EditWindow) {
editWindow = (EditWindow) frame;
}
break;
}
}
}
return editWindow;
}
public RobocodeCompiler getCompiler() {
return Container.getComponent(RobocodeCompilerFactory.class).createCompiler(this);
}
public JDesktopPane getDesktopPane() {
if (desktopPane == null) {
desktopPane = new JDesktopPane();
desktopPane.setBackground(new Color(128, 128, 128));
desktopPane.setPreferredSize(new Dimension(600, 500));
}
return desktopPane;
}
private JLabel getLineLabel() {
if (lineLabel == null) {
lineLabel = new JLabel();
}
return lineLabel;
}
private JPanel getRobocodeEditorContentPane() {
if (robocodeEditorContentPane == null) {
robocodeEditorContentPane = new JPanel();
robocodeEditorContentPane.setLayout(new BorderLayout());
robocodeEditorContentPane.add(getDesktopPane(), "Center");
robocodeEditorContentPane.add(getStatusBar(), "South");
}
return robocodeEditorContentPane;
}
private RobocodeEditorMenuBar getRobocodeEditorMenuBar() {
if (robocodeEditorMenuBar == null) {
robocodeEditorMenuBar = new RobocodeEditorMenuBar(this);
}
return robocodeEditorMenuBar;
}
private JToolBar getStatusBar() {
if (statusBar == null) {
statusBar = new JToolBar();
statusBar.setLayout(new BorderLayout());
statusBar.add(getLineLabel(), BorderLayout.WEST);
}
return statusBar;
}
public FindReplaceDialog getFindReplaceDialog() {
if (findReplaceDialog == null) {
findReplaceDialog = new FindReplaceDialog(this);
}
return findReplaceDialog;
}
public Action getReplaceAction() {
if (replaceAction == null) {
replaceAction = new ReplaceAction();
}
return replaceAction;
}
public void addToWindowMenu(EditWindow window) {
WindowMenuItem item = new WindowMenuItem(window, getRobocodeEditorMenuBar().getWindowMenu());
getRobocodeEditorMenuBar().getMoreWindowsDialog().addWindowItem(item);
}
public void removeFromWindowMenu(EditWindow window) {
for (Component c : getRobocodeEditorMenuBar().getWindowMenu().getMenuComponents()) {
if (c instanceof WindowMenuItem) {
WindowMenuItem item = (WindowMenuItem) c;
if (item.getEditWindow() == window) {
getRobocodeEditorMenuBar().getWindowMenu().remove(item);
getRobocodeEditorMenuBar().getMoreWindowsDialog().removeWindowItem(item);
break;
}
}
}
}
private void initialize() {
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
close();
}
});
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
setIconImage(ImageUtil.getImage("/net/sf/robocode/ui/icons/robocode-icon.png"));
setTitle("Robot Editor");
setJMenuBar(getRobocodeEditorMenuBar());
setContentPane(getRobocodeEditorContentPane());
addComponentListener(eventHandler);
}
public static void main(String[] args) {
try {
// Set the Look and Feel (LAF)
final IWindowManager windowManager = Container.getComponent(IWindowManager.class);
windowManager.setLookAndFeel();
RobocodeEditor robocodeEditor = Container.getComponent(RobocodeEditor.class);
robocodeEditor.isApplication = true; // used for close
robocodeEditor.pack();
// Center robocodeEditor
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension size = robocodeEditor.getSize();
if (size.height > screenSize.height) {
size.height = screenSize.height;
}
if (size.width > screenSize.width) {
size.width = screenSize.width;
}
robocodeEditor.setLocation((screenSize.width - size.width) / 2, (screenSize.height - size.height) / 2);
robocodeEditor.setVisible(true);
// 2nd time for bug in some JREs
robocodeEditor.setVisible(true);
} catch (Throwable e) {
Logger.logError("Exception in RoboCodeEditor.main", e);
}
}
public void openRobot() {
if (editorDirectory == null) {
editorDirectory = robotsDirectory;
}
JFileChooser chooser;
chooser = new JFileChooser(editorDirectory);
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.isHidden()) {
return false;
}
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);
int rv = chooser.showOpenDialog(this);
if (rv == JFileChooser.APPROVE_OPTION) {
String robotFilename = chooser.getSelectedFile().getPath();
editorDirectory = chooser.getSelectedFile().getParentFile();
FileReader fileReader = null;
try {
fileReader = new FileReader(robotFilename);
EditWindow editWindow = new EditWindow(repositoryManager, this, robotsDirectory);
editWindow.getEditorPane().read(fileReader, new File(robotFilename));
editWindow.getEditorPane().setCaretPosition(0);
editWindow.setFileName(robotFilename);
editWindow.setModified(false);
Document d = editWindow.getEditorPane().getDocument();
if (d instanceof JavaDocument) {
((JavaDocument) d).setEditing(true);
}
addPlaceShowFocus(editWindow);
} catch (Exception e) {
JOptionPane.showMessageDialog(this, e.toString());
Logger.logError(e);
} finally {
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException ignored) {}
}
}
}
}
public void extractRobot() {
windowManager.showRobotExtractor(this);
}
public void run() {
getCompiler();
}
public void saveAsRobot() {
EditWindow editWindow = getActiveWindow();
if (editWindow != null) {
editWindow.fileSaveAs();
}
}
public void resetCompilerProperties() {
CompilerProperties props = Container.getComponent(RobocodeCompilerFactory.class).getCompilerProperties();
props.resetCompiler();
Container.getComponent(RobocodeCompilerFactory.class).saveCompilerProperties();
getCompiler();
}
public void saveRobot() {
EditWindow editWindow = getActiveWindow();
if (editWindow != null) {
editWindow.fileSave(false);
}
}
public void setLineStatus(int line) {
if (line >= 0) {
getLineLabel().setText("Line: " + (line + 1));
} else {
getLineLabel().setText("");
}
}
public void showHelpApi() {
String helpurl = "file:" + new File(FileUtil.getCwd(), "").getAbsoluteFile() + File.separator + "javadoc"
+ File.separator + "index.html";
try {
BrowserManager.openURL(helpurl);
} catch (IOException e) {
JOptionPane.showMessageDialog(this, e.getMessage(), "Unable to open browser!",
JOptionPane.INFORMATION_MESSAGE);
}
}
public void setSaveFileMenuItemsEnabled(boolean enabled) {
robocodeEditorMenuBar.getFileSaveAsMenuItem().setEnabled(enabled);
robocodeEditorMenuBar.getFileSaveMenuItem().setEnabled(enabled);
}
}