Added edit decal with file change notifications. Added super simple
WatchService which integrates better with our code by activly making the callbacks.
This commit is contained in:
parent
593c639a06
commit
6f0cf6826a
@ -4,7 +4,9 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface Attachment extends Comparable<Attachment> {
|
||||
import net.sf.openrocket.util.ChangeSource;
|
||||
|
||||
public interface Attachment extends Comparable<Attachment>, ChangeSource {
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
|
@ -11,6 +11,7 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.EventListener;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@ -19,7 +20,11 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.sf.openrocket.appearance.DecalImage;
|
||||
import net.sf.openrocket.document.attachments.BaseAttachment;
|
||||
import net.sf.openrocket.document.attachments.FileSystemAttachment;
|
||||
import net.sf.openrocket.gui.watcher.FileWatcher;
|
||||
import net.sf.openrocket.gui.watcher.WatchEvent;
|
||||
import net.sf.openrocket.gui.watcher.WatchService;
|
||||
import net.sf.openrocket.logging.LogHelper;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.util.BugException;
|
||||
@ -28,12 +33,33 @@ import net.sf.openrocket.util.FileUtils;
|
||||
public class DecalRegistry {
|
||||
private static LogHelper log = Application.getLogger();
|
||||
|
||||
private WatchService watchService = Application.getWatchService();
|
||||
|
||||
DecalRegistry() {
|
||||
|
||||
}
|
||||
|
||||
private Map<String, DecalImageImpl> registeredDecals = new HashMap<String, DecalImageImpl>();
|
||||
|
||||
public DecalImage makeUniqueImage(DecalImage original) {
|
||||
|
||||
if (!(original instanceof DecalImageImpl)) {
|
||||
return original;
|
||||
}
|
||||
|
||||
DecalImageImpl o = (DecalImageImpl) original;
|
||||
|
||||
DecalImageImpl newDecal = o.clone();
|
||||
|
||||
String newName = makeUniqueName(o.getName());
|
||||
|
||||
newDecal.name = newName;
|
||||
|
||||
registeredDecals.put(newName, newDecal);
|
||||
|
||||
return newDecal;
|
||||
|
||||
}
|
||||
|
||||
public DecalImage getDecalImage(Attachment attachment) {
|
||||
String decalName = attachment.getName();
|
||||
DecalImageImpl d;
|
||||
@ -73,7 +99,7 @@ public class DecalRegistry {
|
||||
return decals;
|
||||
}
|
||||
|
||||
public class DecalImageImpl implements DecalImage {
|
||||
public class DecalImageImpl implements DecalImage, Cloneable {
|
||||
|
||||
private final Attachment delegate;
|
||||
|
||||
@ -94,15 +120,61 @@ public class DecalRegistry {
|
||||
return name != null ? name : delegate.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns an InputStream backed by a byte[] containing the decal pixels.
|
||||
* If it reads in the bytes from an actual file, the underlying file is closed.
|
||||
*
|
||||
* @return InputStream containing byte[] of the image
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public InputStream getBytes() throws FileNotFoundException, IOException {
|
||||
return DecalRegistry.getDecal(this);
|
||||
// First check if the decal is located on the file system
|
||||
File exportedFile = getFileSystemLocation();
|
||||
if (exportedFile != null) {
|
||||
InputStream rawIs = new FileInputStream(exportedFile);
|
||||
try {
|
||||
byte[] bytes = FileUtils.readBytes(rawIs);
|
||||
return new ByteArrayInputStream(bytes);
|
||||
} finally {
|
||||
rawIs.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return delegate.getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportImage(File file, boolean watchForChanges) throws IOException {
|
||||
DecalRegistry.exportDecal(this, file);
|
||||
this.fileSystemLocation = file;
|
||||
try {
|
||||
InputStream is = getBytes();
|
||||
OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
|
||||
|
||||
FileUtils.copy(is, os);
|
||||
|
||||
is.close();
|
||||
os.close();
|
||||
|
||||
this.fileSystemLocation = file;
|
||||
|
||||
if (watchForChanges) {
|
||||
watchService.register(new FileWatcher(this.fileSystemLocation) {
|
||||
|
||||
@Override
|
||||
public void handleEvent(WatchEvent evt) {
|
||||
((BaseAttachment) DecalImageImpl.this.delegate).fireChangeEvent();
|
||||
System.out.println(this.getFile() + " has changed");
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
} catch (IOException iex) {
|
||||
throw new BugException(iex);
|
||||
}
|
||||
}
|
||||
|
||||
File getFileSystemLocation() {
|
||||
@ -126,51 +198,25 @@ public class DecalRegistry {
|
||||
return getName().compareTo(o.getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns an InputStream backed by a byte[] containing the decal pixels.
|
||||
* If it reads in the bytes from an actual file, the underlying file is closed.
|
||||
*
|
||||
* @param name
|
||||
* @return
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
private static InputStream getDecal(DecalImageImpl decal) throws FileNotFoundException, IOException {
|
||||
|
||||
// First check if the decal is located on the file system
|
||||
File exportedFile = decal.getFileSystemLocation();
|
||||
if (exportedFile != null) {
|
||||
InputStream rawIs = new FileInputStream(exportedFile);
|
||||
try {
|
||||
byte[] bytes = FileUtils.readBytes(rawIs);
|
||||
return new ByteArrayInputStream(bytes);
|
||||
} finally {
|
||||
rawIs.close();
|
||||
}
|
||||
@Override
|
||||
protected DecalImageImpl clone() {
|
||||
DecalImageImpl clone = new DecalImageImpl(this.delegate);
|
||||
clone.fileSystemLocation = this.fileSystemLocation;
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
return decal.delegate.getBytes();
|
||||
|
||||
}
|
||||
|
||||
private static void exportDecal(DecalImageImpl decal, File selectedFile) throws IOException {
|
||||
|
||||
try {
|
||||
InputStream is = decal.getBytes();
|
||||
OutputStream os = new BufferedOutputStream(new FileOutputStream(selectedFile));
|
||||
|
||||
FileUtils.copy(is, os);
|
||||
|
||||
is.close();
|
||||
os.close();
|
||||
|
||||
} catch (IOException iex) {
|
||||
throw new BugException(iex);
|
||||
@Override
|
||||
public void addChangeListener(EventListener listener) {
|
||||
delegate.addChangeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeChangeListener(EventListener listener) {
|
||||
delegate.removeChangeListener(listener);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private DecalImageImpl findDecalForFile(File file) {
|
||||
@ -203,14 +249,17 @@ public class DecalRegistry {
|
||||
* group(3) = "null"
|
||||
* group(4) = "png"
|
||||
*/
|
||||
private static final Pattern fileNamePattern = Pattern.compile("(.*?)( \\((\\d+)\\)\\)?+)?\\.(\\w*)");
|
||||
private static final Pattern fileNamePattern = Pattern.compile("(.*?)( \\((\\d+)\\)+)?\\.(\\w*)");
|
||||
private static final int BASE_NAME_INDEX = 1;
|
||||
private static final int NUMBER_INDEX = 3;
|
||||
private static final int EXTENSION_INDEX = 4;
|
||||
|
||||
private String makeUniqueName(String name) {
|
||||
|
||||
String newName = "decals/" + name;
|
||||
String newName = name;
|
||||
if (!newName.startsWith("decals/")) {
|
||||
newName = "decals/" + name;
|
||||
}
|
||||
String basename = "";
|
||||
String extension = "";
|
||||
Matcher nameMatcher = fileNamePattern.matcher(newName);
|
||||
|
@ -3,11 +3,14 @@ package net.sf.openrocket.document;
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.sf.openrocket.appearance.Appearance;
|
||||
import net.sf.openrocket.appearance.Decal;
|
||||
import net.sf.openrocket.appearance.DecalImage;
|
||||
import net.sf.openrocket.document.events.DocumentChangeEvent;
|
||||
import net.sf.openrocket.document.events.DocumentChangeListener;
|
||||
@ -18,6 +21,7 @@ import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
|
||||
import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
|
||||
import net.sf.openrocket.rocketcomponent.Configuration;
|
||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.simulation.FlightDataType;
|
||||
import net.sf.openrocket.simulation.customexpression.CustomExpression;
|
||||
import net.sf.openrocket.simulation.listeners.SimulationListener;
|
||||
@ -206,6 +210,31 @@ public class OpenRocketDocument implements ComponentChangeListener {
|
||||
|
||||
}
|
||||
|
||||
public int countDecalUsage(DecalImage img) {
|
||||
int count = 0;
|
||||
|
||||
Iterator<RocketComponent> it = rocket.iterator();
|
||||
while (it.hasNext()) {
|
||||
RocketComponent comp = it.next();
|
||||
Appearance a = comp.getAppearance();
|
||||
if (a == null) {
|
||||
continue;
|
||||
}
|
||||
Decal d = a.getTexture();
|
||||
if (d == null) {
|
||||
continue;
|
||||
}
|
||||
if (img.equals(d.getImage())) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public DecalImage makeUniqueDecal(DecalImage img) {
|
||||
return decalRegistry.makeUniqueImage(img);
|
||||
}
|
||||
|
||||
public DecalImage getDecalImage(Attachment a) {
|
||||
return decalRegistry.getDecalImage(a);
|
||||
}
|
||||
|
@ -5,8 +5,9 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.sf.openrocket.document.Attachment;
|
||||
import net.sf.openrocket.util.AbstractChangeSource;
|
||||
|
||||
public abstract class BaseAttachment implements Attachment {
|
||||
public abstract class BaseAttachment extends AbstractChangeSource implements Attachment {
|
||||
|
||||
private final String name;
|
||||
|
||||
@ -36,5 +37,9 @@ public abstract class BaseAttachment implements Attachment {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fireChangeEvent() {
|
||||
super.fireChangeEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
25
core/src/net/sf/openrocket/file/AttachmentUtils.java
Normal file
25
core/src/net/sf/openrocket/file/AttachmentUtils.java
Normal file
@ -0,0 +1,25 @@
|
||||
package net.sf.openrocket.file;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.sf.openrocket.document.Attachment;
|
||||
import net.sf.openrocket.util.FileUtils;
|
||||
|
||||
public abstract class AttachmentUtils {
|
||||
|
||||
public static void exportAttachment(Attachment a, File outFile) throws IOException {
|
||||
InputStream is = a.getBytes();
|
||||
OutputStream os = new BufferedOutputStream(new FileOutputStream(outFile));
|
||||
|
||||
FileUtils.copy(is, os);
|
||||
|
||||
is.close();
|
||||
os.close();
|
||||
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import javax.swing.JPanel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.appearance.DecalImage;
|
||||
import net.sf.openrocket.document.OpenRocketDocument;
|
||||
import net.sf.openrocket.file.AttachmentUtils;
|
||||
import net.sf.openrocket.gui.util.SwingPreferences;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
@ -78,10 +79,10 @@ public class ExportDecalDialog extends JDialog {
|
||||
private void export(DecalImage decal, File selectedFile) {
|
||||
|
||||
try {
|
||||
decal.exportImage(selectedFile, false);
|
||||
AttachmentUtils.exportAttachment(decal, selectedFile);
|
||||
} catch (IOException iex) {
|
||||
// FIXME - probably want a simple user dialog here since FileIO is not really a bug.
|
||||
throw new BugException(iex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -228,7 +228,7 @@ public class AppearancePanel extends JPanel {
|
||||
ab.addChangeListener(new StateChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(EventObject e) {
|
||||
editBtn.setEnabled(ab.getImage() == null);
|
||||
editBtn.setEnabled(ab.getImage() != null);
|
||||
}
|
||||
});
|
||||
editBtn.addActionListener(new ActionListener() {
|
||||
@ -236,7 +236,7 @@ public class AppearancePanel extends JPanel {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
EditDecalHelper.editDecal(SwingUtilities.getWindowAncestor(AppearancePanel.this), ab.getImage());
|
||||
EditDecalHelper.editDecal(SwingUtilities.getWindowAncestor(AppearancePanel.this), document, c, ab.getImage());
|
||||
} catch (IOException ex) {
|
||||
throw new BugException(ex);
|
||||
}
|
||||
|
@ -24,9 +24,9 @@ import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
|
||||
public class EditDecalDialog extends JDialog {
|
||||
|
||||
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
|
||||
private JRadioButton systemRadio;
|
||||
private JRadioButton commandRadio;
|
||||
private JTextArea commandText;
|
||||
@ -34,88 +34,111 @@ public class EditDecalDialog extends JDialog {
|
||||
private JCheckBox savePref;
|
||||
|
||||
private boolean isCancel = false;
|
||||
private boolean editOne = true;
|
||||
|
||||
public EditDecalDialog(final Window owner) {
|
||||
public EditDecalDialog(final Window owner, boolean promptForEditor, int usageCount) {
|
||||
super(owner, trans.get("EditDecalDialog.title"), Dialog.ModalityType.APPLICATION_MODAL);
|
||||
|
||||
JPanel panel = new JPanel(new MigLayout("fill, ins para"));
|
||||
|
||||
JLabel selectLbl = new JLabel(trans.get("EditDecalDialog.lbl.select"));
|
||||
panel.add(selectLbl, "gapright, wrap");
|
||||
|
||||
ButtonGroup execGroup = new ButtonGroup();
|
||||
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.EDIT) ) {
|
||||
|
||||
systemRadio = new JRadioButton(trans.get("EditDecalDialog.lbl.system"));
|
||||
systemRadio.setSelected(false);
|
||||
panel.add(systemRadio,"wrap");
|
||||
execGroup.add(systemRadio);
|
||||
JPanel panel = new JPanel(new MigLayout("fill, ins para"));
|
||||
|
||||
if (promptForEditor) {
|
||||
JLabel selectLbl = new JLabel(trans.get("EditDecalDialog.lbl.select"));
|
||||
panel.add(selectLbl, "gapright, wrap");
|
||||
|
||||
commandRadio = new JRadioButton(trans.get("EditDecalDialog.lbl.cmdline"));
|
||||
commandRadio.setSelected(false);
|
||||
panel.add(commandRadio,"wrap");
|
||||
execGroup.add(commandRadio);
|
||||
ButtonGroup execGroup = new ButtonGroup();
|
||||
|
||||
commandText = new JTextArea();
|
||||
commandText.setEnabled(false);
|
||||
panel.add(commandText, "growx, wrap");
|
||||
|
||||
final JButton chooser = new JButton(trans.get("EditDecalDialog.btn.chooser"));
|
||||
chooser.setEnabled(false);
|
||||
chooser.addActionListener( new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JFileChooser fc = new JFileChooser();
|
||||
int action = fc.showOpenDialog(owner);
|
||||
if ( action == JFileChooser.APPROVE_OPTION) {
|
||||
commandText.setText(fc.getSelectedFile().getAbsolutePath());
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.EDIT)) {
|
||||
|
||||
systemRadio = new JRadioButton(trans.get("EditDecalDialog.lbl.system"));
|
||||
systemRadio.setSelected(true);
|
||||
panel.add(systemRadio, "wrap");
|
||||
execGroup.add(systemRadio);
|
||||
|
||||
commandRadio = new JRadioButton(trans.get("EditDecalDialog.lbl.cmdline"));
|
||||
commandRadio.setSelected(false);
|
||||
panel.add(commandRadio, "wrap");
|
||||
execGroup.add(commandRadio);
|
||||
|
||||
commandText = new JTextArea();
|
||||
commandText.setEnabled(false);
|
||||
panel.add(commandText, "growx, wrap");
|
||||
|
||||
final JButton chooser = new JButton(trans.get("EditDecalDialog.btn.chooser"));
|
||||
chooser.setEnabled(false);
|
||||
chooser.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JFileChooser fc = new JFileChooser();
|
||||
int action = fc.showOpenDialog(owner);
|
||||
if (action == JFileChooser.APPROVE_OPTION) {
|
||||
commandText.setText(fc.getSelectedFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
panel.add(chooser, "growx, wrap");
|
||||
|
||||
});
|
||||
panel.add(chooser, "growx, wrap");
|
||||
|
||||
|
||||
commandRadio.addChangeListener( new ChangeListener() {
|
||||
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
boolean enabled = commandRadio.isSelected();
|
||||
commandText.setEnabled(enabled);
|
||||
chooser.setEnabled(enabled);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} else {
|
||||
commandText = new JTextArea();
|
||||
commandText.setEnabled(false);
|
||||
panel.add(commandText, "growx, wrap");
|
||||
|
||||
final JButton chooser = new JButton(trans.get("EditDecalDialog.btn.chooser"));
|
||||
chooser.setEnabled(false);
|
||||
chooser.addActionListener( new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JFileChooser fc = new JFileChooser();
|
||||
int action = fc.showOpenDialog(owner);
|
||||
if ( action == JFileChooser.APPROVE_OPTION) {
|
||||
commandText.setText(fc.getSelectedFile().getAbsolutePath());
|
||||
commandRadio.addChangeListener(new ChangeListener() {
|
||||
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
boolean enabled = commandRadio.isSelected();
|
||||
commandText.setEnabled(enabled);
|
||||
chooser.setEnabled(enabled);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
panel.add(chooser, "growx, wrap");
|
||||
|
||||
} else {
|
||||
commandText = new JTextArea();
|
||||
commandText.setEnabled(false);
|
||||
panel.add(commandText, "growx, wrap");
|
||||
|
||||
final JButton chooser = new JButton(trans.get("EditDecalDialog.btn.chooser"));
|
||||
chooser.setEnabled(false);
|
||||
chooser.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JFileChooser fc = new JFileChooser();
|
||||
int action = fc.showOpenDialog(owner);
|
||||
if (action == JFileChooser.APPROVE_OPTION) {
|
||||
commandText.setText(fc.getSelectedFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
panel.add(chooser, "growx, wrap");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
savePref = new JCheckBox(trans.get("EditDecalDialog.lbl.always"));
|
||||
panel.add(savePref,"wrap");
|
||||
if (usageCount > 1) {
|
||||
ButtonGroup bg = new ButtonGroup();
|
||||
final JRadioButton justThisOne = new JRadioButton("just this one", true);
|
||||
justThisOne.addChangeListener(new ChangeListener() {
|
||||
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
EditDecalDialog.this.editOne = justThisOne.isSelected();
|
||||
}
|
||||
|
||||
});
|
||||
panel.add(justThisOne, "left");
|
||||
bg.add(justThisOne);
|
||||
JRadioButton all = new JRadioButton("all", false);
|
||||
panel.add(all, "gapleft para, right, wrap");
|
||||
bg.add(all);
|
||||
}
|
||||
|
||||
if (promptForEditor) {
|
||||
savePref = new JCheckBox(trans.get("EditDecalDialog.lbl.always"));
|
||||
panel.add(savePref, "wrap");
|
||||
}
|
||||
|
||||
// OK / Cancel buttons
|
||||
JButton okButton = new JButton(trans.get("dlg.but.ok"));
|
||||
@ -141,10 +164,9 @@ public class EditDecalDialog extends JDialog {
|
||||
|
||||
GUIUtil.rememberWindowSize(this);
|
||||
GUIUtil.setDisposableDialogOptions(this, okButton);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public boolean isCancel() {
|
||||
return isCancel;
|
||||
}
|
||||
@ -154,22 +176,25 @@ public class EditDecalDialog extends JDialog {
|
||||
}
|
||||
|
||||
public boolean isUseSystemEditor() {
|
||||
return systemRadio!= null && systemRadio.isSelected();
|
||||
return systemRadio != null && systemRadio.isSelected();
|
||||
}
|
||||
|
||||
public String getCommandLine() {
|
||||
return commandText.getText();
|
||||
}
|
||||
|
||||
public boolean isEditOne() {
|
||||
return editOne;
|
||||
}
|
||||
|
||||
public void ok() {
|
||||
isCancel = false;
|
||||
this.setVisible(false);
|
||||
}
|
||||
|
||||
|
||||
public void close() {
|
||||
isCancel = true;
|
||||
this.setVisible(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -29,9 +29,9 @@ public class FigureRenderer extends RocketRenderer {
|
||||
|
||||
GL2 gl = drawable.getGL().getGL2();
|
||||
|
||||
gl.glLightModelfv(GL2ES1.GL_LIGHT_MODEL_AMBIENT,
|
||||
new float[] { 0,0,0 }, 0);
|
||||
|
||||
gl.glLightModelfv(GL2ES1.GL_LIGHT_MODEL_AMBIENT,
|
||||
new float[] { 0, 0, 0 }, 0);
|
||||
|
||||
float amb = 0.3f;
|
||||
float dif = 1.0f - amb;
|
||||
float spc = 1.0f;
|
||||
@ -41,21 +41,21 @@ public class FigureRenderer extends RocketRenderer {
|
||||
new float[] { dif, dif, dif, 1 }, 0);
|
||||
gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_SPECULAR,
|
||||
new float[] { spc, spc, spc, 1 }, 0);
|
||||
|
||||
|
||||
gl.glEnable(GLLightingFunc.GL_LIGHT1);
|
||||
gl.glEnable(GLLightingFunc.GL_LIGHTING);
|
||||
gl.glShadeModel(GLLightingFunc.GL_SMOOTH);
|
||||
|
||||
|
||||
gl.glEnable(GLLightingFunc.GL_NORMALIZE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isDrawn(RocketComponent c) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isDrawnTransparent(RocketComponent c) {
|
||||
if (c instanceof BodyTube)
|
||||
@ -74,7 +74,7 @@ public class FigureRenderer extends RocketRenderer {
|
||||
}
|
||||
|
||||
private static final HashMap<Class<?>, Color> defaultColorCache = new HashMap<Class<?>, Color>();
|
||||
|
||||
|
||||
@Override
|
||||
public void renderComponent(GL2 gl, RocketComponent c, float alpha) {
|
||||
|
||||
@ -88,13 +88,13 @@ public class FigureRenderer extends RocketRenderer {
|
||||
defaultColorCache.put(c.getClass(), figureColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set up the front A&D color
|
||||
convertColor(figureColor, color);
|
||||
color[3] = alpha;
|
||||
gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, color, 0);
|
||||
gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, color, 0);
|
||||
|
||||
|
||||
// Set up the Specular color & Shine
|
||||
convertColor(figureColor, color);
|
||||
float d = 0.9f;
|
||||
@ -102,13 +102,13 @@ public class FigureRenderer extends RocketRenderer {
|
||||
color[0] = Math.max(color[0], d) * m;
|
||||
color[1] = Math.max(color[1], d) * m;
|
||||
color[2] = Math.max(color[2], d) * m;
|
||||
|
||||
|
||||
gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_SPECULAR, color, 0);
|
||||
gl.glMateriali(GL.GL_FRONT, GLLightingFunc.GL_SHININESS, getShine(c));
|
||||
|
||||
color[0] = color[1] = color[2] = 0;
|
||||
gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_SPECULAR, color, 0);
|
||||
|
||||
|
||||
//Back A&D
|
||||
convertColor(figureColor, color);
|
||||
color[0] = color[0] * 0.4f;
|
||||
@ -117,10 +117,14 @@ public class FigureRenderer extends RocketRenderer {
|
||||
color[3] = alpha;
|
||||
gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_DIFFUSE, color, 0);
|
||||
gl.glMaterialfv(GL.GL_BACK, GLLightingFunc.GL_AMBIENT, color, 0);
|
||||
|
||||
|
||||
cr.renderGeometry(gl, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushTextureCache(GLAutoDrawable drawable) {
|
||||
}
|
||||
|
||||
private static int getShine(RocketComponent c) {
|
||||
if (c instanceof ExternalComponent) {
|
||||
switch (((ExternalComponent) c).getFinish()) {
|
||||
@ -140,9 +144,9 @@ public class FigureRenderer extends RocketRenderer {
|
||||
}
|
||||
return 20;
|
||||
}
|
||||
|
||||
|
||||
protected static void convertColor(Color color, float[] out) {
|
||||
if ( color == null ){
|
||||
if (color == null) {
|
||||
out[0] = 1;
|
||||
out[1] = 1;
|
||||
out[2] = 0;
|
||||
|
@ -169,6 +169,13 @@ public class RealisticRenderer extends RocketRenderer {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushTextureCache(GLAutoDrawable drawable) {
|
||||
// Flush the cache twice to get rid of old images.
|
||||
clearCaches(drawable.getGL().getGL2());
|
||||
clearCaches(drawable.getGL().getGL2());
|
||||
}
|
||||
|
||||
private void clearCaches(GL2 gl) {
|
||||
log.debug("ClearCaches");
|
||||
for (Map.Entry<String, Texture> e : oldTexCache.entrySet()) {
|
||||
|
@ -106,6 +106,16 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
public void flushTextureCaches() {
|
||||
canvas.invoke(true, new GLRunnable() {
|
||||
@Override
|
||||
public boolean run(GLAutoDrawable drawable) {
|
||||
rr.flushTextureCache(drawable);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if 3d view is enabled. This may be toggled by the user at
|
||||
* launch time.
|
||||
|
@ -48,6 +48,8 @@ public abstract class RocketRenderer {
|
||||
|
||||
public abstract boolean isDrawnTransparent(RocketComponent c);
|
||||
|
||||
public abstract void flushTextureCache(GLAutoDrawable drawable);
|
||||
|
||||
public RocketComponent pick(GLAutoDrawable drawable, Configuration configuration, Point p,
|
||||
Set<RocketComponent> ignore) {
|
||||
final GL2 gl = drawable.getGL().getGL2();
|
||||
|
@ -61,6 +61,7 @@ import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.masscalc.BasicMassCalculator;
|
||||
import net.sf.openrocket.masscalc.MassCalculator;
|
||||
import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
|
||||
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
|
||||
import net.sf.openrocket.rocketcomponent.Configuration;
|
||||
import net.sf.openrocket.rocketcomponent.Rocket;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
@ -87,71 +88,71 @@ import net.sf.openrocket.util.StateChangeListener;
|
||||
*/
|
||||
public class RocketPanel extends JPanel implements TreeSelectionListener, ChangeSource {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
|
||||
/*RocketPanel.FigTypeAct.Sideview = Side view
|
||||
RocketPanel.FigTypeAct.Backview = Back view
|
||||
RocketPanel.FigViewAct.3DFigure = 3D Figure
|
||||
RocketPanel.FigViewAct.3DRealistic = 3D Realistic*/
|
||||
|
||||
|
||||
private static enum VIEW_TYPE {
|
||||
Sideview,
|
||||
Backview,
|
||||
Figure3D,
|
||||
Realistic3D;
|
||||
@Override
|
||||
public String toString(){
|
||||
public String toString() {
|
||||
return trans.get("RocketPanel.FigTypeAct." + super.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private boolean is3d;
|
||||
private final RocketFigure figure;
|
||||
private final RocketFigure3d figure3d;
|
||||
|
||||
|
||||
|
||||
|
||||
private final ScaleScrollPane scrollPane;
|
||||
|
||||
|
||||
private final JPanel figureHolder;
|
||||
|
||||
|
||||
private JLabel infoMessage;
|
||||
|
||||
|
||||
private TreeSelectionModel selectionModel = null;
|
||||
|
||||
|
||||
private BasicSlider rotationSlider;
|
||||
private ScaleSelector scaleSelector;
|
||||
|
||||
|
||||
|
||||
|
||||
/* Calculation of CP and CG */
|
||||
private AerodynamicCalculator aerodynamicCalculator;
|
||||
private MassCalculator massCalculator;
|
||||
|
||||
|
||||
|
||||
|
||||
private final OpenRocketDocument document;
|
||||
private final Configuration configuration;
|
||||
|
||||
|
||||
private Caret extraCP = null;
|
||||
private Caret extraCG = null;
|
||||
private RocketInfo extraText = null;
|
||||
|
||||
|
||||
|
||||
|
||||
private double cpAOA = Double.NaN;
|
||||
private double cpTheta = Double.NaN;
|
||||
private double cpMach = Double.NaN;
|
||||
private double cpRoll = Double.NaN;
|
||||
|
||||
|
||||
// The functional ID of the rocket that was simulated
|
||||
private int flightDataFunctionalID = -1;
|
||||
private String flightDataMotorID = null;
|
||||
|
||||
|
||||
|
||||
|
||||
private SimulationWorker backgroundSimulationWorker = null;
|
||||
|
||||
|
||||
private List<EventListener> listeners = new ArrayList<EventListener>();
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The executor service used for running the background simulations.
|
||||
* This uses a fixed-sized thread pool for all background simulations
|
||||
@ -161,37 +162,37 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
static {
|
||||
backgroundSimulationExecutor = Executors.newFixedThreadPool(SwingPreferences.getMaxThreadCount(),
|
||||
new ThreadFactory() {
|
||||
private ThreadFactory factory = Executors.defaultThreadFactory();
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = factory.newThread(r);
|
||||
t.setDaemon(true);
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
private ThreadFactory factory = Executors.defaultThreadFactory();
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = factory.newThread(r);
|
||||
t.setDaemon(true);
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public RocketPanel(OpenRocketDocument document) {
|
||||
|
||||
|
||||
this.document = document;
|
||||
configuration = document.getDefaultConfiguration();
|
||||
|
||||
|
||||
// TODO: FUTURE: calculator selection
|
||||
aerodynamicCalculator = new BarrowmanCalculator();
|
||||
massCalculator = new BasicMassCalculator();
|
||||
|
||||
|
||||
// Create figure and custom scroll pane
|
||||
figure = new RocketFigure(configuration);
|
||||
figure3d = new RocketFigure3d(document, configuration);
|
||||
|
||||
|
||||
figureHolder = new JPanel(new BorderLayout());
|
||||
|
||||
|
||||
scrollPane = new ScaleScrollPane(figure) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent event) {
|
||||
handleMouseClick(event);
|
||||
@ -199,21 +200,28 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
};
|
||||
scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
|
||||
scrollPane.setFitting(true);
|
||||
|
||||
|
||||
createPanel();
|
||||
|
||||
|
||||
is3d = true;
|
||||
go2D();
|
||||
|
||||
|
||||
configuration.addChangeListener(new StateChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(EventObject e) {
|
||||
// System.out.println("Configuration changed, calling updateFigure");
|
||||
if (is3d) {
|
||||
if (e instanceof ComponentChangeEvent) {
|
||||
if (((ComponentChangeEvent) e).isTextureChange()) {
|
||||
figure3d.flushTextureCaches();
|
||||
}
|
||||
}
|
||||
}
|
||||
updateExtras();
|
||||
updateFigures();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
figure3d.addComponentSelectionListener(new RocketFigure3d.ComponentSelectionListener() {
|
||||
@Override
|
||||
public void componentClicked(RocketComponent clicked[], MouseEvent event) {
|
||||
@ -221,14 +229,14 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void updateFigures() {
|
||||
if (!is3d)
|
||||
figure.updateFigure();
|
||||
else
|
||||
figure3d.updateFigure();
|
||||
}
|
||||
|
||||
|
||||
private void go3D() {
|
||||
if (is3d)
|
||||
return;
|
||||
@ -237,13 +245,13 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
figureHolder.add(figure3d, BorderLayout.CENTER);
|
||||
rotationSlider.setEnabled(false);
|
||||
scaleSelector.setEnabled(false);
|
||||
|
||||
|
||||
revalidate();
|
||||
figureHolder.revalidate();
|
||||
|
||||
|
||||
figure3d.repaint();
|
||||
}
|
||||
|
||||
|
||||
private void go2D() {
|
||||
if (!is3d)
|
||||
return;
|
||||
@ -256,16 +264,16 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
figureHolder.revalidate();
|
||||
figure.repaint();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the layout and components of the panel.
|
||||
*/
|
||||
private void createPanel() {
|
||||
setLayout(new MigLayout("", "[shrink][grow]", "[shrink][shrink][grow][shrink]"));
|
||||
|
||||
|
||||
setPreferredSize(new Dimension(800, 300));
|
||||
|
||||
|
||||
|
||||
|
||||
// View Type Dropdown
|
||||
ComboBoxModel cm = new DefaultComboBoxModel(VIEW_TYPE.values()) {
|
||||
@Override
|
||||
@ -295,84 +303,84 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
add(new JLabel("View Type:"), "spanx, split");
|
||||
add(new JComboBox(cm));
|
||||
|
||||
|
||||
|
||||
// Zoom level selector
|
||||
scaleSelector = new ScaleSelector(scrollPane);
|
||||
add(scaleSelector);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Stage selector
|
||||
StageSelector stageSelector = new StageSelector(configuration);
|
||||
add(stageSelector);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Flight configuration selector
|
||||
//// Flight configuration:
|
||||
JLabel label = new JLabel(trans.get("RocketPanel.lbl.Flightcfg"));
|
||||
label.setHorizontalAlignment(JLabel.RIGHT);
|
||||
add(label, "growx, right");
|
||||
add(new JComboBox(new FlightConfigurationModel(configuration)), "");
|
||||
|
||||
|
||||
//// Edit button
|
||||
JButton button = new JButton(trans.get("RocketPanel.but.FlightcfgEdit"));
|
||||
button.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JDialog configDialog = new FlightConfigurationDialog(document.getRocket(),SwingUtilities.windowForComponent(RocketPanel.this));
|
||||
JDialog configDialog = new FlightConfigurationDialog(document.getRocket(), SwingUtilities.windowForComponent(RocketPanel.this));
|
||||
configDialog.show();
|
||||
}
|
||||
});
|
||||
add(button, "wrap");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Create slider and scroll pane
|
||||
|
||||
|
||||
DoubleModel theta = new DoubleModel(figure, "Rotation",
|
||||
UnitGroup.UNITS_ANGLE, 0, 2 * Math.PI);
|
||||
UnitSelector us = new UnitSelector(theta, true);
|
||||
us.setHorizontalAlignment(JLabel.CENTER);
|
||||
add(us, "alignx 50%, growx");
|
||||
|
||||
|
||||
// Add the rocket figure
|
||||
add(figureHolder, "grow, spany 2, wmin 300lp, hmin 100lp, wrap");
|
||||
|
||||
|
||||
|
||||
|
||||
// Add rotation slider
|
||||
// Minimum size to fit "360deg"
|
||||
JLabel l = new JLabel("360" + Chars.DEGREE);
|
||||
Dimension d = l.getPreferredSize();
|
||||
|
||||
|
||||
add(rotationSlider = new BasicSlider(theta.getSliderModel(0, 2 * Math.PI), JSlider.VERTICAL, true),
|
||||
"ax 50%, wrap, width " + (d.width + 6) + "px:null:null, growy");
|
||||
|
||||
|
||||
|
||||
|
||||
//// <html>Click to select Shift+click to select other Double-click to edit Click+drag to move
|
||||
infoMessage = new JLabel(trans.get("RocketPanel.lbl.infoMessage"));
|
||||
infoMessage.setFont(new Font("Sans Serif", Font.PLAIN, 9));
|
||||
add(infoMessage, "skip, span, gapleft 25, wrap");
|
||||
|
||||
|
||||
|
||||
|
||||
addExtras();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public RocketFigure getFigure() {
|
||||
return figure;
|
||||
}
|
||||
|
||||
|
||||
public AerodynamicCalculator getAerodynamicCalculator() {
|
||||
return aerodynamicCalculator;
|
||||
}
|
||||
|
||||
|
||||
public Configuration getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the center of pressure figure element.
|
||||
*
|
||||
@ -381,7 +389,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
public Caret getExtraCP() {
|
||||
return extraCP;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the center of gravity figure element.
|
||||
*
|
||||
@ -390,7 +398,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
public Caret getExtraCG() {
|
||||
return extraCG;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the extra text figure element.
|
||||
*
|
||||
@ -399,7 +407,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
public RocketInfo getExtraText() {
|
||||
return extraText;
|
||||
}
|
||||
|
||||
|
||||
public void setSelectionModel(TreeSelectionModel m) {
|
||||
if (selectionModel != null) {
|
||||
selectionModel.removeTreeSelectionListener(this);
|
||||
@ -408,9 +416,9 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
selectionModel.addTreeSelectionListener(this);
|
||||
valueChanged((TreeSelectionEvent) null); // updates FigureParameters
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return the angle of attack used in CP calculation. NaN signifies the default value
|
||||
* of zero.
|
||||
@ -419,7 +427,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
public double getCPAOA() {
|
||||
return cpAOA;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the angle of attack to be used in CP calculation. A value of NaN signifies that
|
||||
* the default AOA (zero) should be used.
|
||||
@ -434,11 +442,11 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
updateFigures();
|
||||
fireChangeEvent();
|
||||
}
|
||||
|
||||
|
||||
public double getCPTheta() {
|
||||
return cpTheta;
|
||||
}
|
||||
|
||||
|
||||
public void setCPTheta(double theta) {
|
||||
if (MathUtil.equals(theta, cpTheta) ||
|
||||
(Double.isNaN(theta) && Double.isNaN(cpTheta)))
|
||||
@ -450,11 +458,11 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
updateFigures();
|
||||
fireChangeEvent();
|
||||
}
|
||||
|
||||
|
||||
public double getCPMach() {
|
||||
return cpMach;
|
||||
}
|
||||
|
||||
|
||||
public void setCPMach(double mach) {
|
||||
if (MathUtil.equals(mach, cpMach) ||
|
||||
(Double.isNaN(mach) && Double.isNaN(cpMach)))
|
||||
@ -464,11 +472,11 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
updateFigures();
|
||||
fireChangeEvent();
|
||||
}
|
||||
|
||||
|
||||
public double getCPRoll() {
|
||||
return cpRoll;
|
||||
}
|
||||
|
||||
|
||||
public void setCPRoll(double roll) {
|
||||
if (MathUtil.equals(roll, cpRoll) ||
|
||||
(Double.isNaN(roll) && Double.isNaN(cpRoll)))
|
||||
@ -478,31 +486,31 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
updateFigures();
|
||||
fireChangeEvent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void addChangeListener(EventListener listener) {
|
||||
listeners.add(0, listener);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void removeChangeListener(EventListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
protected void fireChangeEvent() {
|
||||
EventObject e = new EventObject(this);
|
||||
for (EventListener l : listeners) {
|
||||
if ( l instanceof StateChangeListener ) {
|
||||
((StateChangeListener)l).stateChanged(e);
|
||||
if (l instanceof StateChangeListener) {
|
||||
((StateChangeListener) l).stateChanged(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Handle clicking on figure shapes. The functioning is the following:
|
||||
*
|
||||
@ -513,7 +521,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
* the next component. Otherwise select the first component in the list.
|
||||
*/
|
||||
public static final int CYCLE_SELECTION_MODIFIER = InputEvent.SHIFT_DOWN_MASK;
|
||||
|
||||
|
||||
private void handleMouseClick(MouseEvent event) {
|
||||
if (event.getButton() != MouseEvent.BUTTON1)
|
||||
return;
|
||||
@ -521,20 +529,20 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
Point p1 = scrollPane.getViewport().getViewPosition();
|
||||
int x = p0.x + p1.x;
|
||||
int y = p0.y + p1.y;
|
||||
|
||||
|
||||
RocketComponent[] clicked = figure.getComponentsByPoint(x, y);
|
||||
|
||||
|
||||
handleComponentClick(clicked, event);
|
||||
}
|
||||
|
||||
private void handleComponentClick(RocketComponent[] clicked, MouseEvent event){
|
||||
|
||||
|
||||
private void handleComponentClick(RocketComponent[] clicked, MouseEvent event) {
|
||||
|
||||
// If no component is clicked, do nothing
|
||||
if (clicked.length == 0){
|
||||
if (clicked.length == 0) {
|
||||
selectionModel.setSelectionPath(null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check whether the currently selected component is in the clicked components.
|
||||
TreePath path = selectionModel.getSelectionPath();
|
||||
if (path != null) {
|
||||
@ -551,7 +559,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Currently selected component not clicked
|
||||
if (path == null) {
|
||||
if (event.isShiftDown() && event.getClickCount() == 1 && clicked.length > 1) {
|
||||
@ -560,34 +568,34 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
path = ComponentTreeModel.makeTreePath(clicked[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set selection and check for double-click
|
||||
selectionModel.setSelectionPath(path);
|
||||
if (event.getClickCount() == 2) {
|
||||
RocketComponent component = (RocketComponent) path.getLastPathComponent();
|
||||
|
||||
|
||||
ComponentConfigDialog.showDialog(SwingUtilities.getWindowAncestor(this),
|
||||
document, component);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Updates the extra data included in the figure. Currently this includes
|
||||
* the CP and CG carets.
|
||||
*/
|
||||
private WarningSet warnings = new WarningSet();
|
||||
|
||||
|
||||
private void updateExtras() {
|
||||
Coordinate cp, cg;
|
||||
double cpx, cgx;
|
||||
|
||||
|
||||
// TODO: MEDIUM: User-definable conditions
|
||||
FlightConditions conditions = new FlightConditions(configuration);
|
||||
warnings.clear();
|
||||
|
||||
|
||||
if (!Double.isNaN(cpMach)) {
|
||||
conditions.setMach(cpMach);
|
||||
extraText.setMach(cpMach);
|
||||
@ -595,20 +603,20 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
conditions.setMach(Application.getPreferences().getDefaultMach());
|
||||
extraText.setMach(Application.getPreferences().getDefaultMach());
|
||||
}
|
||||
|
||||
|
||||
if (!Double.isNaN(cpAOA)) {
|
||||
conditions.setAOA(cpAOA);
|
||||
} else {
|
||||
conditions.setAOA(0);
|
||||
}
|
||||
extraText.setAOA(cpAOA);
|
||||
|
||||
|
||||
if (!Double.isNaN(cpRoll)) {
|
||||
conditions.setRollRate(cpRoll);
|
||||
} else {
|
||||
conditions.setRollRate(0);
|
||||
}
|
||||
|
||||
|
||||
if (!Double.isNaN(cpTheta)) {
|
||||
conditions.setTheta(cpTheta);
|
||||
cp = aerodynamicCalculator.getCP(configuration, conditions, warnings);
|
||||
@ -616,24 +624,24 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
cp = aerodynamicCalculator.getWorstCP(configuration, conditions, warnings);
|
||||
}
|
||||
extraText.setTheta(cpTheta);
|
||||
|
||||
|
||||
|
||||
|
||||
cg = massCalculator.getCG(configuration, MassCalcType.LAUNCH_MASS);
|
||||
// System.out.println("CG computed as "+cg+ " CP as "+cp);
|
||||
|
||||
|
||||
if (cp.weight > 0.000001)
|
||||
cpx = cp.x;
|
||||
else
|
||||
cpx = Double.NaN;
|
||||
|
||||
|
||||
if (cg.weight > 0.000001)
|
||||
cgx = cg.x;
|
||||
else
|
||||
cgx = Double.NaN;
|
||||
|
||||
|
||||
figure3d.setCG(cg);
|
||||
figure3d.setCP(cp);
|
||||
|
||||
|
||||
// Length bound is assumed to be tight
|
||||
double length = 0, diameter = 0;
|
||||
Collection<Coordinate> bounds = configuration.getBounds();
|
||||
@ -647,7 +655,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
}
|
||||
length = maxX - minX;
|
||||
}
|
||||
|
||||
|
||||
for (RocketComponent c : configuration) {
|
||||
if (c instanceof SymmetricComponent) {
|
||||
double d1 = ((SymmetricComponent) c).getForeRadius() * 2;
|
||||
@ -655,31 +663,31 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
diameter = MathUtil.max(diameter, d1, d2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extraText.setCG(cgx);
|
||||
extraText.setCP(cpx);
|
||||
extraText.setLength(length);
|
||||
extraText.setDiameter(diameter);
|
||||
extraText.setMass(cg.weight);
|
||||
extraText.setWarnings(warnings);
|
||||
|
||||
|
||||
|
||||
|
||||
if (figure.getType() == RocketFigure.TYPE_SIDE && length > 0) {
|
||||
|
||||
|
||||
// TODO: LOW: Y-coordinate and rotation
|
||||
extraCP.setPosition(cpx * RocketFigure.EXTRA_SCALE, 0);
|
||||
extraCG.setPosition(cgx * RocketFigure.EXTRA_SCALE, 0);
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
|
||||
extraCP.setPosition(Double.NaN, Double.NaN);
|
||||
extraCG.setPosition(Double.NaN, Double.NaN);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//////// Flight simulation in background
|
||||
|
||||
|
||||
// Check whether to compute or not
|
||||
if (!((SwingPreferences) Application.getPreferences()).computeFlightInBackground()) {
|
||||
extraText.setFlightData(null);
|
||||
@ -687,38 +695,38 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
stopBackgroundSimulation();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check whether data is already up to date
|
||||
if (flightDataFunctionalID == configuration.getRocket().getFunctionalModID() &&
|
||||
flightDataMotorID == configuration.getFlightConfigurationID()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
flightDataFunctionalID = configuration.getRocket().getFunctionalModID();
|
||||
flightDataMotorID = configuration.getFlightConfigurationID();
|
||||
|
||||
|
||||
// Stop previous computation (if any)
|
||||
stopBackgroundSimulation();
|
||||
|
||||
|
||||
// Check that configuration has motors
|
||||
if (!configuration.hasMotors()) {
|
||||
extraText.setFlightData(FlightData.NaN_DATA);
|
||||
extraText.setCalculatingData(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Start calculation process
|
||||
extraText.setCalculatingData(true);
|
||||
|
||||
|
||||
Rocket duplicate = (Rocket) configuration.getRocket().copy();
|
||||
Simulation simulation = ((SwingPreferences)Application.getPreferences()).getBackgroundSimulation(duplicate);
|
||||
Simulation simulation = ((SwingPreferences) Application.getPreferences()).getBackgroundSimulation(duplicate);
|
||||
simulation.getOptions().setMotorConfigurationID(
|
||||
configuration.getFlightConfigurationID());
|
||||
|
||||
|
||||
backgroundSimulationWorker = new BackgroundSimulationWorker(document, simulation);
|
||||
backgroundSimulationExecutor.execute(backgroundSimulationWorker);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cancels the current background simulation worker, if any.
|
||||
*/
|
||||
@ -728,26 +736,26 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
backgroundSimulationWorker = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A SimulationWorker that simulates the rocket flight in the background and
|
||||
* sets the results to the extra text when finished. The worker can be cancelled
|
||||
* if necessary.
|
||||
*/
|
||||
private class BackgroundSimulationWorker extends SimulationWorker {
|
||||
|
||||
|
||||
private final CustomExpressionSimulationListener exprListener;
|
||||
|
||||
|
||||
public BackgroundSimulationWorker(OpenRocketDocument doc, Simulation sim) {
|
||||
super(sim);
|
||||
List<CustomExpression> exprs = doc.getCustomExpressions();
|
||||
exprListener = new CustomExpressionSimulationListener(exprs);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected FlightData doInBackground() {
|
||||
|
||||
|
||||
// Pause a little while to allow faster UI reaction
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
@ -755,38 +763,38 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
}
|
||||
if (isCancelled() || backgroundSimulationWorker != this)
|
||||
return null;
|
||||
|
||||
|
||||
return super.doInBackground();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void simulationDone() {
|
||||
// Do nothing if cancelled
|
||||
if (isCancelled() || backgroundSimulationWorker != this)
|
||||
return;
|
||||
|
||||
|
||||
backgroundSimulationWorker = null;
|
||||
extraText.setFlightData(simulation.getSimulatedData());
|
||||
extraText.setCalculatingData(false);
|
||||
figure.repaint();
|
||||
figure3d.repaint();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected SimulationListener[] getExtraListeners() {
|
||||
return new SimulationListener[] {
|
||||
InterruptListener.INSTANCE,
|
||||
ApogeeEndListener.INSTANCE,
|
||||
exprListener};
|
||||
|
||||
exprListener };
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void simulationInterrupted(Throwable t) {
|
||||
// Do nothing on cancel, set N/A data otherwise
|
||||
if (isCancelled() || backgroundSimulationWorker != this) // Double-check
|
||||
return;
|
||||
|
||||
|
||||
backgroundSimulationWorker = null;
|
||||
extraText.setFlightData(FlightData.NaN_DATA);
|
||||
extraText.setCalculatingData(false);
|
||||
@ -794,9 +802,9 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
figure3d.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Adds the extra data to the figure. Currently this includes the CP and CG carets.
|
||||
*/
|
||||
@ -805,21 +813,21 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
extraCP = new CPCaret(0, 0);
|
||||
extraText = new RocketInfo(configuration);
|
||||
updateExtras();
|
||||
|
||||
|
||||
figure.clearRelativeExtra();
|
||||
figure.addRelativeExtra(extraCP);
|
||||
figure.addRelativeExtra(extraCG);
|
||||
figure.addAbsoluteExtra(extraText);
|
||||
|
||||
|
||||
|
||||
|
||||
figure3d.clearRelativeExtra();
|
||||
//figure3d.addRelativeExtra(extraCP);
|
||||
//figure3d.addRelativeExtra(extraCG);
|
||||
figure3d.addAbsoluteExtra(extraText);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Updates the selection in the FigureParameters and repaints the figure.
|
||||
* Ignores the event itself.
|
||||
@ -832,17 +840,17 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
figure3d.setSelection(null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
RocketComponent[] components = new RocketComponent[paths.length];
|
||||
for (int i = 0; i < paths.length; i++)
|
||||
components[i] = (RocketComponent) paths[i].getLastPathComponent();
|
||||
figure.setSelection(components);
|
||||
|
||||
|
||||
figure3d.setSelection(components);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An <code>Action</code> that shows whether the figure type is the type
|
||||
* given in the constructor.
|
||||
@ -852,13 +860,13 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
private class FigureTypeAction extends AbstractAction implements StateChangeListener {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final int type;
|
||||
|
||||
|
||||
public FigureTypeAction(int type) {
|
||||
this.type = type;
|
||||
stateChanged(null);
|
||||
figure.addChangeListener(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
boolean state = (Boolean) getValue(Action.SELECTED_KEY);
|
||||
@ -870,11 +878,11 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
|
||||
}
|
||||
stateChanged(null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void stateChanged(EventObject e) {
|
||||
putValue(Action.SELECTED_KEY, figure.getType() == type && !is3d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -5,91 +5,107 @@ import java.awt.Window;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.sf.openrocket.appearance.AppearanceBuilder;
|
||||
import net.sf.openrocket.appearance.DecalImage;
|
||||
import net.sf.openrocket.document.OpenRocketDocument;
|
||||
import net.sf.openrocket.gui.dialogs.EditDecalDialog;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
|
||||
public class EditDecalHelper {
|
||||
|
||||
|
||||
// FIXME - need to have a specific set of localizable exceptions come out of this instead of generic IOException;
|
||||
// perhaps - unable to create file,
|
||||
// unable to open system editor
|
||||
// unable to fork process
|
||||
|
||||
private static final SwingPreferences prefs = ((SwingPreferences)Application.getPreferences());
|
||||
|
||||
public static void editDecal( Window parent, DecalImage decal ) throws IOException {
|
||||
|
||||
private static final SwingPreferences prefs = ((SwingPreferences) Application.getPreferences());
|
||||
|
||||
public static void editDecal(Window parent, OpenRocketDocument doc, RocketComponent component, DecalImage decal) throws IOException {
|
||||
|
||||
boolean sysPrefSet = prefs.isDecalEditorPreferenceSet();
|
||||
int usageCount = doc.countDecalUsage(decal);
|
||||
|
||||
//First Check preferences
|
||||
if (sysPrefSet && usageCount == 1) {
|
||||
|
||||
launchEditor(prefs.isDecalEditorPreferenceSystem(), prefs.getDecalEditorCommandLine(), decal);
|
||||
return;
|
||||
}
|
||||
|
||||
EditDecalDialog dialog = new EditDecalDialog(parent, !sysPrefSet, usageCount);
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.isCancel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do we use the System Preference Editor or from the dialog?
|
||||
boolean useSystemEditor = false;
|
||||
String commandLine = "";
|
||||
|
||||
if (sysPrefSet) {
|
||||
useSystemEditor = prefs.isDecalEditorPreferenceSystem();
|
||||
commandLine = prefs.getDecalEditorCommandLine();
|
||||
} else {
|
||||
useSystemEditor = dialog.isUseSystemEditor();
|
||||
commandLine = dialog.getCommandLine();
|
||||
// Do we need to save the preferences?
|
||||
if (dialog.isSavePreferences()) {
|
||||
prefs.setDecalEditorPreference(useSystemEditor, commandLine);
|
||||
}
|
||||
}
|
||||
|
||||
if (dialog.isEditOne()) {
|
||||
decal = makeDecalUnique(doc, component, decal);
|
||||
}
|
||||
|
||||
launchEditor(useSystemEditor, commandLine, decal);
|
||||
|
||||
}
|
||||
|
||||
private static DecalImage makeDecalUnique(OpenRocketDocument doc, RocketComponent component, DecalImage decal) {
|
||||
|
||||
DecalImage newImage = doc.makeUniqueDecal(decal);
|
||||
|
||||
AppearanceBuilder appearanceBuilder = new AppearanceBuilder(component.getAppearance());
|
||||
appearanceBuilder.setImage(newImage);
|
||||
|
||||
component.setAppearance(appearanceBuilder.getAppearance());
|
||||
|
||||
return newImage;
|
||||
}
|
||||
|
||||
private static void launchEditor(boolean useSystemEditor, String commandTemplate, DecalImage decal) throws IOException {
|
||||
|
||||
String decalId = decal.getName();
|
||||
// Create Temp File.
|
||||
int dotlocation = decalId.lastIndexOf('.');
|
||||
String extension = "tmp";
|
||||
if ( dotlocation > 0 && dotlocation < decalId.length() ) {
|
||||
if (dotlocation > 0 && dotlocation < decalId.length()) {
|
||||
extension = decalId.substring(dotlocation);
|
||||
}
|
||||
File tmpFile = File.createTempFile("OR_graphics", extension);
|
||||
|
||||
decal.exportImage(tmpFile, true);
|
||||
|
||||
//First Check preferences
|
||||
if ( prefs.isDecalEditorPreferenceSet() ) {
|
||||
|
||||
if (useSystemEditor) {
|
||||
Desktop.getDesktop().edit(tmpFile);
|
||||
} else {
|
||||
|
||||
// FIXME - need this one or all dialog.
|
||||
String filename = tmpFile.getAbsolutePath();
|
||||
|
||||
if ( prefs.isDecalEditorPreferenceSystem() ) {
|
||||
launchSystemEditor( tmpFile );
|
||||
String command;
|
||||
if (commandTemplate.contains("%%")) {
|
||||
command = commandTemplate.replace("%%", filename);
|
||||
} else {
|
||||
String commandTemplate = prefs.getDecalEditorCommandLine();
|
||||
launchCommandEditor(commandTemplate, tmpFile);
|
||||
command = commandTemplate + " " + filename;
|
||||
}
|
||||
return;
|
||||
|
||||
Runtime.getRuntime().exec(command);
|
||||
|
||||
}
|
||||
|
||||
// Preference not set, launch dialog
|
||||
EditDecalDialog dialog = new EditDecalDialog(parent);
|
||||
dialog.setVisible(true);
|
||||
|
||||
if( dialog.isCancel() ) {
|
||||
// FIXME - delete tmpfile?
|
||||
return;
|
||||
}
|
||||
|
||||
boolean saveToPrefs = dialog.isSavePreferences();
|
||||
|
||||
if ( dialog.isUseSystemEditor() ) {
|
||||
if ( saveToPrefs ) {
|
||||
prefs.setDecalEditorPreference(true, null);
|
||||
}
|
||||
launchSystemEditor( tmpFile );
|
||||
} else {
|
||||
String commandLine = dialog.getCommandLine();
|
||||
if( saveToPrefs ) {
|
||||
prefs.setDecalEditorPreference(false, commandLine);
|
||||
}
|
||||
launchCommandEditor( commandLine, tmpFile );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void launchSystemEditor( File tmpFile ) throws IOException {
|
||||
|
||||
Desktop.getDesktop().edit(tmpFile);
|
||||
|
||||
}
|
||||
|
||||
private static void launchCommandEditor( String commandTemplate, File tmpFile ) throws IOException {
|
||||
|
||||
String filename = tmpFile.getAbsolutePath();
|
||||
|
||||
String command;
|
||||
if( commandTemplate.contains("%%")) {
|
||||
command = commandTemplate.replace("%%", filename);
|
||||
} else {
|
||||
command = commandTemplate + " " + filename;
|
||||
}
|
||||
|
||||
Runtime.getRuntime().exec(command);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
37
core/src/net/sf/openrocket/gui/watcher/FileWatcher.java
Normal file
37
core/src/net/sf/openrocket/gui/watcher/FileWatcher.java
Normal file
@ -0,0 +1,37 @@
|
||||
package net.sf.openrocket.gui.watcher;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public abstract class FileWatcher implements Watchable {
|
||||
|
||||
private final File file;
|
||||
private long lastModifiedTimestamp = 0L;
|
||||
|
||||
public FileWatcher(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
protected File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WatchEvent monitor() {
|
||||
|
||||
long modified = file.lastModified();
|
||||
if (modified == 0L) {
|
||||
// check for removal?
|
||||
return null;
|
||||
}
|
||||
if (modified > lastModifiedTimestamp) {
|
||||
long oldTimestamp = lastModifiedTimestamp;
|
||||
lastModifiedTimestamp = modified;
|
||||
return (oldTimestamp == 0L) ? null : WatchEvent.MODIFIED;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void handleEvent(WatchEvent evt);
|
||||
|
||||
}
|
8
core/src/net/sf/openrocket/gui/watcher/WatchEvent.java
Normal file
8
core/src/net/sf/openrocket/gui/watcher/WatchEvent.java
Normal file
@ -0,0 +1,8 @@
|
||||
package net.sf.openrocket.gui.watcher;
|
||||
|
||||
public enum WatchEvent {
|
||||
|
||||
MODIFIED,
|
||||
REMOVED;
|
||||
|
||||
}
|
7
core/src/net/sf/openrocket/gui/watcher/WatchKey.java
Normal file
7
core/src/net/sf/openrocket/gui/watcher/WatchKey.java
Normal file
@ -0,0 +1,7 @@
|
||||
package net.sf.openrocket.gui.watcher;
|
||||
|
||||
public interface WatchKey {
|
||||
|
||||
public void cancel();
|
||||
|
||||
}
|
7
core/src/net/sf/openrocket/gui/watcher/WatchService.java
Normal file
7
core/src/net/sf/openrocket/gui/watcher/WatchService.java
Normal file
@ -0,0 +1,7 @@
|
||||
package net.sf.openrocket.gui.watcher;
|
||||
|
||||
public interface WatchService {
|
||||
|
||||
public abstract WatchKey register(Watchable w);
|
||||
|
||||
}
|
72
core/src/net/sf/openrocket/gui/watcher/WatchServiceImpl.java
Normal file
72
core/src/net/sf/openrocket/gui/watcher/WatchServiceImpl.java
Normal file
@ -0,0 +1,72 @@
|
||||
package net.sf.openrocket.gui.watcher;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class WatchServiceImpl implements WatchService {
|
||||
|
||||
private final static int INTERVAL_MS = 1000;
|
||||
|
||||
private static AtomicInteger threadcount = new AtomicInteger(0);
|
||||
|
||||
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(2, new ThreadFactory() {
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r);
|
||||
t.setName("WatchService-" + threadcount.getAndIncrement());
|
||||
return t;
|
||||
}
|
||||
});
|
||||
|
||||
public WatchServiceImpl() {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.sf.openrocket.gui.watcher.WatchService#register(net.sf.openrocket.gui.watcher.Watchable)
|
||||
*/
|
||||
@Override
|
||||
public WatchKey register(Watchable w) {
|
||||
ScheduledFuture<?> future = executor.scheduleWithFixedDelay(new WatchableRunner(w), 0, INTERVAL_MS, TimeUnit.MILLISECONDS);
|
||||
|
||||
return new WatchKeyImpl(future);
|
||||
}
|
||||
|
||||
public class WatchKeyImpl implements WatchKey {
|
||||
|
||||
ScheduledFuture<?> future;
|
||||
|
||||
private WatchKeyImpl(ScheduledFuture<?> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
future.cancel(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class WatchableRunner implements Runnable {
|
||||
|
||||
private Watchable w;
|
||||
|
||||
private WatchableRunner(Watchable w) {
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
WatchEvent evt = w.monitor();
|
||||
if (evt != null) {
|
||||
w.handleEvent(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
9
core/src/net/sf/openrocket/gui/watcher/Watchable.java
Normal file
9
core/src/net/sf/openrocket/gui/watcher/Watchable.java
Normal file
@ -0,0 +1,9 @@
|
||||
package net.sf.openrocket.gui.watcher;
|
||||
|
||||
public interface Watchable {
|
||||
|
||||
public WatchEvent monitor();
|
||||
|
||||
public void handleEvent(WatchEvent evt);
|
||||
|
||||
}
|
@ -5,7 +5,7 @@ import java.util.EventObject;
|
||||
public class ComponentChangeEvent extends EventObject {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
|
||||
/** A change that does not affect simulation results in any way (name, color, etc.) */
|
||||
public static final int NONFUNCTIONAL_CHANGE = 1;
|
||||
/** A change that affects the mass properties of the rocket */
|
||||
@ -23,6 +23,8 @@ public class ComponentChangeEvent extends EventObject {
|
||||
public static final int MOTOR_CHANGE = 32;
|
||||
/** A change that affects the events occurring during flight. */
|
||||
public static final int EVENT_CHANGE = 64;
|
||||
/** A change to the 3D texture assigned to a component*/
|
||||
public static final int TEXTURE_CHANGE = 128;
|
||||
|
||||
/** A bit-field that contains all possible change types. */
|
||||
public static final int ALL_CHANGE = 0xFFFFFFFF;
|
||||
@ -47,6 +49,9 @@ public class ComponentChangeEvent extends EventObject {
|
||||
return (RocketComponent) super.getSource();
|
||||
}
|
||||
|
||||
public boolean isTextureChange() {
|
||||
return (type & TEXTURE_CHANGE) != 0;
|
||||
}
|
||||
|
||||
public boolean isAerodynamicChange() {
|
||||
return (type & AERODYNAMIC_CHANGE) != 0;
|
||||
|
@ -2,11 +2,13 @@ package net.sf.openrocket.rocketcomponent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.EventListener;
|
||||
import java.util.EventObject;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import net.sf.openrocket.appearance.Appearance;
|
||||
import net.sf.openrocket.appearance.Decal;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.preset.ComponentPreset;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
@ -20,6 +22,7 @@ import net.sf.openrocket.util.LineStyle;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
import net.sf.openrocket.util.SafetyMutex;
|
||||
import net.sf.openrocket.util.SimpleStack;
|
||||
import net.sf.openrocket.util.StateChangeListener;
|
||||
import net.sf.openrocket.util.UniqueID;
|
||||
|
||||
|
||||
@ -423,6 +426,17 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
*/
|
||||
public void setAppearance(Appearance appearance) {
|
||||
this.appearance = appearance;
|
||||
Decal d = this.appearance.getTexture();
|
||||
if (d != null) {
|
||||
d.getImage().addChangeListener(new StateChangeListener() {
|
||||
|
||||
@Override
|
||||
public void stateChanged(EventObject e) {
|
||||
fireComponentChangeEvent(ComponentChangeEvent.TEXTURE_CHANGE);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package net.sf.openrocket.startup;
|
||||
import net.sf.openrocket.database.ComponentPresetDao;
|
||||
import net.sf.openrocket.database.motor.MotorDatabase;
|
||||
import net.sf.openrocket.database.motor.ThrustCurveMotorSetDatabase;
|
||||
import net.sf.openrocket.gui.watcher.WatchService;
|
||||
import net.sf.openrocket.l10n.ClassBasedTranslator;
|
||||
import net.sf.openrocket.l10n.DebugTranslator;
|
||||
import net.sf.openrocket.l10n.ExceptionSuppressingTranslator;
|
||||
@ -68,7 +69,9 @@ public final class Application {
|
||||
Application.logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public static WatchService getWatchService() {
|
||||
return Application.injector.getInstance(WatchService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the log buffer.
|
||||
|
@ -1,20 +1,23 @@
|
||||
package net.sf.openrocket.startup;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
import net.sf.openrocket.gui.watcher.WatchService;
|
||||
import net.sf.openrocket.gui.watcher.WatchServiceImpl;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.logging.LogHelper;
|
||||
import net.sf.openrocket.util.watcher.DirectoryChangeReactor;
|
||||
import net.sf.openrocket.util.watcher.DirectoryChangeReactorImpl;
|
||||
|
||||
public class ApplicationModule extends AbstractModule {
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
public class ApplicationModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(LogHelper.class).toInstance(Application.getLogger());
|
||||
bind(Preferences.class).toInstance(Application.getPreferences());
|
||||
bind(Translator.class).toInstance(Application.getTranslator());
|
||||
bind(DirectoryChangeReactor.class).to(DirectoryChangeReactorImpl.class);
|
||||
bind(DirectoryChangeReactor.class).to(DirectoryChangeReactorImpl.class);
|
||||
bind(WatchService.class).to(WatchServiceImpl.class);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user