Merge remote-tracking branch 'plaa/kruland-integration' into

kruland-integration-defaults

Conflicts:
	core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
This commit is contained in:
bkuker 2013-01-26 10:37:11 -05:00
commit 9ae80e5a84
25 changed files with 554 additions and 195 deletions

View File

@ -4,6 +4,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.EventListener;
import net.sf.openrocket.appearance.DecalImage;
import net.sf.openrocket.document.Attachment;
@ -42,4 +43,14 @@ class ResourceDecalImage implements DecalImage {
return this.hashCode() - a.hashCode();
}
@Override
public void addChangeListener(EventListener listener) {
//Unimplemented, this can not change
}
@Override
public void removeChangeListener(EventListener listener) {
//Unimplemented, this can not change
}
}

View File

@ -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();

View File

@ -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);

View File

@ -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);
}

View File

@ -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();
}
}

View 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();
}
}

View File

@ -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);
}
}
}

View File

@ -244,7 +244,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() {
@ -252,7 +252,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);
}

View File

@ -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);
}
}

View File

@ -123,6 +123,10 @@ public class FigureRenderer extends RocketRenderer {
cr.renderGeometry(gl, c);
}
@Override
public void flushTextureCache(GLAutoDrawable drawable) {
}
private static int getShine(RocketComponent c) {
if (c instanceof ExternalComponent) {
switch (((ExternalComponent) c).getFinish()) {

View File

@ -190,6 +190,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()) {

View File

@ -107,6 +107,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.

View File

@ -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();

View File

@ -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;
@ -214,6 +215,13 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
@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();
}

View File

@ -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);
}
}

View 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);
}

View File

@ -0,0 +1,8 @@
package net.sf.openrocket.gui.watcher;
public enum WatchEvent {
MODIFIED,
REMOVED;
}

View File

@ -0,0 +1,7 @@
package net.sf.openrocket.gui.watcher;
public interface WatchKey {
public void cancel();
}

View File

@ -0,0 +1,7 @@
package net.sf.openrocket.gui.watcher;
public interface WatchService {
public abstract WatchKey register(Watchable w);
}

View 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);
}
}
}
}

View File

@ -0,0 +1,9 @@
package net.sf.openrocket.gui.watcher;
public interface Watchable {
public WatchEvent monitor();
public void handleEvent(WatchEvent evt);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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.

View File

@ -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);
}
}