diff --git a/core/ChangeLog b/core/ChangeLog index 996fc3e76..4fff51e05 100644 --- a/core/ChangeLog +++ b/core/ChangeLog @@ -1,3 +1,9 @@ +2012-09-27 Bill Kuker + + * Added user preference to enable/disable 3D, and crash detection to disable 3D on a crash + + * Moved JOGL GL initialization earlier in initialization. + 2012-09-25 Doug Pedrick * Added preference to open last edited design file upon startup. diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index c3a959e4e..79ba29659 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -52,6 +52,8 @@ RocketPanel.FigViewAct.2D = 2D View RocketPanel.FigViewAct.ttip.2D = 2D View RocketPanel.FigViewAct.3D = 3D View RocketPanel.FigViewAct.ttip.3D = 3D View +RocketPanel.3DDisabledMessage = OpenRocket's 3D capabilities have been disabled by either the user or a crash. +RocketPanel.3DDisabledMessageButton = Re-enable 3D at next application launch. RocketPanel.lbl.Motorcfg = Motor configuration: RocketPanel.lbl.infoMessage = Click to select    Shift+click to select other    Double-click to edit    Click+drag to move @@ -221,6 +223,7 @@ pref.dlg.but.add = Add pref.dlg.but.reset = Reset pref.dlg.but.checknow = Check now pref.dlg.but.openlast = Open last design file on startup +pref.dlg.but.enable3d = Enable 3D pref.dlg.but.defaultmetric = Default metric pref.dlg.but.defaultimperial = Default imperial pref.dlg.title.Preferences = Preferences diff --git a/core/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java b/core/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java index 82cf33d9a..65619c833 100644 --- a/core/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java +++ b/core/src/net/sf/openrocket/gui/dialogs/preferences/PreferencesDialog.java @@ -298,7 +298,19 @@ public class PreferencesDialog extends JDialog { Application.getPreferences().setAutoOpenLastDesignOnStartup(autoOpenDesignFile.isSelected()); } }); - panel.add(autoOpenDesignFile); + panel.add(autoOpenDesignFile, "wrap"); + + + final JCheckBox enable3D = new JCheckBox(trans.get("pref.dlg.but.enable3d")); + enable3D.setSelected(Application.getPreferences().is3dEnabled()); + enable3D.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + Application.getPreferences().set3dEnabled(enable3D.isSelected()); + } + }); + panel.add(enable3D); + return panel; } diff --git a/core/src/net/sf/openrocket/gui/figure3d/OpenGLUtils.java b/core/src/net/sf/openrocket/gui/figure3d/OpenGLUtils.java new file mode 100644 index 000000000..27e33d6b7 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/figure3d/OpenGLUtils.java @@ -0,0 +1,125 @@ +package net.sf.openrocket.gui.figure3d; + +import java.awt.SplashScreen; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +import javax.media.opengl.GLProfile; + +import net.sf.openrocket.arch.SystemInfo; +import net.sf.openrocket.arch.SystemInfo.Platform; +import net.sf.openrocket.gui.main.Splash; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.startup.Application; + +public class OpenGLUtils { + private static final LogHelper log = Application.getLogger(); + + /** + * Keep the state of 3D consistent for the entire launch, so if user enables + * 3d and opens a new window it stays disabled. + */ + private static Boolean enabledThisLaunch = null; + + /** + * set true in enterDangerZone, false in exitDangerZone allows the exit + * function to return immediately if called extra times + */ + private static boolean inTheDangerZone = false; + + /** + * Call this method as early as possible. + */ + public static void earlyInitialize() { + // If crash detection fails this will allow someone to disable + // the 3d preference from the command line. + if (System.getProperty("openrocket.3d.disable") != null) { + Application.getPreferences().set3dEnabled(false); + } + + if (!is3dEnabled()) { + log.debug("OpenGL is disabled"); + } else { + log.debug("Initializing OpenGL"); + enterDangerZone(); + if (SystemInfo.getPlatform() == Platform.UNIX) { + log.debug("Dismissing splash screen (Linux/Java/JOGL bug)"); + // Fixes a linux / X bug: Splash must be closed before GL Init + SplashScreen splash = Splash.getSplashScreen(); + if (splash != null && splash.isVisible()) + splash.close(); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // Intentionally Ignored + } + } + log.debug("Calling GLProfile.initSingleton()"); + GLProfile.initSingleton(); + exitDangerZone(); + } + } + + /** + * Returns true if 3d functions are enabled + * + * @return + */ + static boolean is3dEnabled() { + if (enabledThisLaunch == null) + enabledThisLaunch = new Boolean(Application.getPreferences().is3dEnabled()); + return enabledThisLaunch.booleanValue(); + } + + /** + * Signal that we are about to do something that can cause a GL crash. If + * exitDangerZone is not called after this the 3D user preference will be + * disabled at the next startup. + */ + static void enterDangerZone() { + log.verbose("Entering GL DangerZone"); + inTheDangerZone = true; + Application.getPreferences().set3dEnabled(false); + try { + Preferences.userRoot().flush(); + } catch (BackingStoreException e) { + log.warn("Unable to flush prefs in enterDangerZone()"); + } + } + + /** + * Signal that some GL operation has succeeded. the UserPreference will be + * left alone. + * + * Safe to call when not in the danger-zone. Safe to call quite often + */ + static void exitDangerZone() { + if (!inTheDangerZone) + return; + inTheDangerZone = false; + log.verbose("Exiting GL DangerZone"); + Application.getPreferences().set3dEnabled(true); + try { + Preferences.userRoot().flush(); + } catch (BackingStoreException e) { + log.warn("Unable to flush prefs in exitDangerZone()"); + } + } + + /** + * Seriously never call this it will segfault the JVM. + @Deprecated + static void segfault(){ + try { + log.error("Segfaulting!"); + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + Unsafe unsafe = (Unsafe)f.get(null); + unsafe.putAddress(0, 0); + } catch (Exception e) { + log.error("Unable to segfault", e); + } + + } + **/ +} diff --git a/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java b/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java index 3cae27189..f162f6604 100644 --- a/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java +++ b/core/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java @@ -6,7 +6,10 @@ import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; -import java.awt.SplashScreen; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.HierarchyEvent; +import java.awt.event.HierarchyListener; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; @@ -25,16 +28,18 @@ import javax.media.opengl.awt.GLCanvas; import javax.media.opengl.fixedfunc.GLLightingFunc; import javax.media.opengl.fixedfunc.GLMatrixFunc; import javax.media.opengl.glu.GLU; +import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.event.MouseInputAdapter; +import net.miginfocom.swing.MigLayout; import net.sf.openrocket.gui.figureelements.CGCaret; import net.sf.openrocket.gui.figureelements.CPCaret; import net.sf.openrocket.gui.figureelements.FigureElement; -import net.sf.openrocket.gui.main.Splash; +import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.rocketcomponent.Configuration; import net.sf.openrocket.rocketcomponent.RocketComponent; @@ -50,6 +55,7 @@ import com.jogamp.opengl.util.awt.Overlay; public class RocketFigure3d extends JPanel implements GLEventListener { private static final long serialVersionUID = 1L; private static final LogHelper log = Application.getLogger(); + private static final Translator trans = Application.getTranslator(); static { //this allows the GL canvas and things like the motor selection @@ -64,8 +70,6 @@ public class RocketFigure3d extends JPanel implements GLEventListener { private Configuration configuration; private GLCanvas canvas; - - private Overlay extrasOverlay, caretOverlay; private BufferedImage cgCaretRaster, cpCaretRaster; private volatile boolean redrawExtras = true; @@ -88,27 +92,49 @@ public class RocketFigure3d extends JPanel implements GLEventListener { this.setLayout(new BorderLayout()); //Only initizlize GL if 3d is enabled. - if (is3dEnabled()) { - //Fixes a linux / X bug: Splash must be closed before GL Init - SplashScreen splash = Splash.getSplashScreen(); - if (splash != null && splash.isVisible()) - splash.close(); - + if ( OpenGLUtils.is3dEnabled() ){ initGLCanvas(); + addHierarchyListener(new HierarchyListener(){ + @Override + public void hierarchyChanged(HierarchyEvent e) { + OpenGLUtils.enterDangerZone(); + RocketFigure3d.this.removeHierarchyListener(this); + } + }); + } else { + setupDisabledUI(); } } /** - * Return true if 3d view is enabled. This may be toggled by the user at - * launch time. - * @return + * Draws a pretty primitive UI that explains that 3D is not enabled. */ - public static boolean is3dEnabled() { - return System.getProperty("openrocket.3d.disable") == null; + private void setupDisabledUI(){ + canvas = null; + final JPanel panel = new JPanel(new MigLayout("filly")); + panel.add(new JLabel(trans.get("RocketPanel.3DDisabledMessage"))); + + if ( !Application.getPreferences().is3dEnabled() ){ + //this button lets them turn GL on without going to the preference menu. + final JButton enable = new JButton(trans.get("RocketPanel.3DDisabledMessageButton")); + enable.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + Application.getPreferences().set3dEnabled(true); + panel.remove(enable); + panel.revalidate(); + panel.repaint(); + } + }); + panel.add(enable); + } + + this.add(panel); } private void initGLCanvas() { log.debug("Initializing RocketFigure3D OpenGL Canvas"); + OpenGLUtils.enterDangerZone(); try { log.debug("Setting up GL capabilities..."); @@ -139,7 +165,7 @@ public class RocketFigure3d extends JPanel implements GLEventListener { log.verbose("GL - Setting up mouse listeners"); setupMouseListeners(); - log.verbose("GL - Rasterizine Carets"); //reticulating splines? + log.verbose("GL - Rasterizing Carets"); //reticulating splines? rasterizeCarets(); } catch (Throwable t) { @@ -147,6 +173,8 @@ public class RocketFigure3d extends JPanel implements GLEventListener { canvas = null; this.add(new JLabel("Unable to load 3d Libraries: " + t.getMessage())); + } finally { + OpenGLUtils.exitDangerZone(); } } @@ -285,6 +313,8 @@ public class RocketFigure3d extends JPanel implements GLEventListener { drawExtras(gl, glu); drawCarets(gl, glu); + + OpenGLUtils.exitDangerZone(); } @@ -567,7 +597,7 @@ public class RocketFigure3d extends JPanel implements GLEventListener { glu.gluProject(c.x, c.y, c.z, mvmatrix, 0, projmatrix, 0, viewport, 0, out, 0); - log.verbose("GL - peoject() complete"); + log.verbose("GL - project() complete"); return new Coordinate(out[0], out[1], out[2]); } diff --git a/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index 2cef70809..8d7890861 100644 --- a/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/core/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -285,7 +285,6 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change } }); bg.add(toggle3d); - toggle3d.setEnabled(RocketFigure3d.is3dEnabled()); add(toggle3d, "gap rel"); // Zoom level selector diff --git a/core/src/net/sf/openrocket/startup/Preferences.java b/core/src/net/sf/openrocket/startup/Preferences.java index 46e1239d0..7c3a4659b 100644 --- a/core/src/net/sf/openrocket/startup/Preferences.java +++ b/core/src/net/sf/openrocket/startup/Preferences.java @@ -49,6 +49,7 @@ public abstract class Preferences { // Node names public static final String PREFERRED_THRUST_CURVE_MOTOR_NODE = "preferredThrustCurveMotors"; private static final String AUTO_OPEN_LAST_DESIGN = "AUTO_OPEN_LAST_DESIGN"; + private static final String THREE_D_ENABLED = "THREE_D_ENABLED"; /* * ****************************************************************************************** @@ -115,6 +116,22 @@ public abstract class Preferences { return this.getBoolean(AUTO_OPEN_LAST_DESIGN, false); } + /** + * Enable/Disable the 3d functionality + * @param enabled + */ + public final void set3dEnabled(boolean enabled){ + this.putBoolean(THREE_D_ENABLED, enabled); + } + + /** + * + * @return true if 3d is enabled. + */ + public final boolean is3dEnabled(){ + return this.getBoolean(THREE_D_ENABLED, true); + } + /** * Return the OpenRocket unique ID. * diff --git a/core/src/net/sf/openrocket/startup/Startup.java b/core/src/net/sf/openrocket/startup/Startup.java index 31f83184a..dd3f36975 100644 --- a/core/src/net/sf/openrocket/startup/Startup.java +++ b/core/src/net/sf/openrocket/startup/Startup.java @@ -4,6 +4,7 @@ import java.io.PrintStream; import java.util.Locale; import java.util.prefs.Preferences; +import net.sf.openrocket.gui.figure3d.OpenGLUtils; import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.l10n.DebugTranslator; import net.sf.openrocket.l10n.L10N; @@ -51,6 +52,8 @@ public class Startup { Application.setPreferences( new SwingPreferences() ); + OpenGLUtils.earlyInitialize(); + // Setup the translations initializeL10n();