--- /dev/null
+#!/bin/bash
+
+#
+# A script to batch-convert all source image files into suitable
+# slideset image files. It converts all *.png, *.jpg and *.xcf.gz
+# files into the suitably sized jpg images in the datafiles directory.
+#
+
+DEST=../../datafiles/tours
+
+CONVERSION="-background #ececec -flatten -geometry 600x400 -quality 85"
+
+
+# Convert all xcf files
+find -iname "*.xcf.gz" | grep -v MANUAL | while read FILE; do
+
+ echo Converting $FILE
+ BASE="$(echo $FILE | sed 's/\.xcf\.gz$//')"
+ xcf2png "$FILE" | convert $CONVERSION - $DEST/$BASE.jpg
+
+done
+
+# Convert all png and jpg files
+find -iname "*.png" -o -iname "*.jpg" | grep -v MANUAL | while read FILE; do
+
+ echo Converting $FILE
+ BASE="$(echo $FILE | sed 's/\.png$//' | sed 's/\.jpg$//')"
+ convert $CONVERSION $FILE $DEST/$BASE.jpg
+
+done
\ No newline at end of file
--- /dev/null
+
+Introduction
+
+<p>This first tour provides a quick overview of OpenRocket screens and
+features.
+
+
+[logo.png]
+# TODO: Add "Welcome to OpenRocket" text to image.
+
+<p><b>OpenRocket</b> is a versatile application for designing,
+simulating and optimizing model rockets. This first tour provides an
+overview of the OpenRocket screens and features.
+
+<p>You can browse through the tour using the <b>Next</b> and
+<b>Previous</b> buttons, or using the <em>left</em> and <em>right
+arrows</em> on your keyboard.
+
+
+[none]
+
+<p>This is the startup screen from which you can create a new rocket
+design or open existing designs.
+
+<p>For this tour, let's open an example design called <em>A simple model
+rocket</em>.
+
+
+[main_window.jpg]
+
+<p>This is the main screen of OpenRocket. It is divided horizontally
+into two parts: the rocket design / flight simulation section and the
+rocket design view.
+
+
+[main_window_top.jpg]
+
+<p>On the top left is the component tree of the rocket design.
+It displays which components are attached to what components.
+
+<p>Next to it are buttons which allow adding new components to the
+rocket.
+
+
+[main_window_bottom.jpg]
+
+<p>The bottom half of the window contains a diagram of the current
+rocket design.
+
+<p>The different viewing options are described in detail in the
+<a href="FIXME">Viewing options</a> tour.
+
+
+[flight_simulations.jpg]
+
+<p>When you select the <b>Flight simulations</b> tab, the top part of
+the window changes to show the simulations that have been defined for
+the rocket.
+
+<p>You can define various simulations with different motor
+configurations and differing launch conditions, such as wind and
+launch rod angle.
+
+
+[simulation_edit.png]
+
+<p>By double-clicking on a simulation you open the <em>Simulation edit
+dialog</em>. On the first two tabs you can define simulation options,
+on the last two tabs you can plot or export the results.
+
+<p>Simulating a rocket is described in detail in the
+<a href="FIXME">Simulating a flight</a> tour.
+
+
+[advanced_features.jpg]
+
+<p>Other advanced features include component analysis and automatic
+design optimization, which are covered by separate tours.
+
+<p>Next you can take a tour on <a href="FIXME">Creating a rocket
+design</a>, browse other tours or start experimenting with the
+software.
+
+
}
p {
- margin: 0 0 5px;
+ margin: 0 0 8px;
}
a {
color: #0000dd;
text-decoration: underline;
}
+
+em {
+ font-style: italics;
+}
\ No newline at end of file
+++ /dev/null
-
-# Foo bar
-
-First test tour
-
-This is the <i>description</i>.
-
-
-[left_design.png]
-
-<p>This is the first slide — the left_design file.
-
-<p>It's nifty. It can also contain <a href="foobar">links</a> (though they don't work yet).
-
-
-
-[main_window.png]
-This is the next slide (and the last).
-
--- /dev/null
+
+# Foo bar
+
+First test tour
+
+This is the <i>description</i>.
+
+
+[left_design.png]
+
+<p>This is the first slide — the left_design file.
+
+<p>It's nifty. It can also contain <a href="foobar">links</a> (though they don't work yet).
+<p>Tour2: <a href="test2/test2.tour">Test2</a>
+<p>Hyperlink: <a href="http://www.google.com">google</a>
+
+
+[main_window.png]
+This is the next slide (and the last).
+
--- /dev/null
+
+Das test Tour
+
+[left_design.png]
+
+Das is ein test tour.
+++ /dev/null
-Another test tour
-
-<p>This is the second tour.
-<p>Comprende?
-
-
-[left_design.png]
-
-This is the second tour.
-
-
-[main_window.png]
-
-This is the second/last slide.
-
--- /dev/null
+Another test tour
+
+<p>This is the second tour.
+<p>Comprende?
+
+
+[left_design.png]
+
+This is the second tour.
+
+
+[main_window.png]
+
+This is the second/last slide.
+
+++ /dev/null
-
-Das test Tour
-
-[left_design.png]
-
-Das is ein test tour.
# This file lists all the available tours.
-test.tour
-test2.tour
+introduction/introduction.tour
+
+
+test1/test.tour
+test2/test2.tour
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
-import java.io.FileNotFoundException;
-import java.io.IOException;
import java.util.List;
import javax.swing.AbstractListModel;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Named;
public class GuidedTourSelectionDialog extends JDialog {
private static final Translator trans = Application.getTranslator();
- private static final String TOURS_BASE_DIR = "datafiles/tours";
-
+
private final SlideSetManager slideSetManager;
private final List<String> tourNames;
public GuidedTourSelectionDialog(Window parent) {
super(parent, trans.get("title"), ModalityType.MODELESS);
- try {
-
- slideSetManager = new SlideSetManager(TOURS_BASE_DIR);
- slideSetManager.load();
-
- tourNames = slideSetManager.getSlideSetNames();
- if (tourNames.isEmpty()) {
- throw new FileNotFoundException("No tours found.");
- }
-
- } catch (IOException e) {
- throw new BugException(e);
- }
+ slideSetManager = SlideSetManager.getSlideSetManager();
+ tourNames = slideSetManager.getSlideSetNames();
-
JPanel panel = new JPanel(new MigLayout("fill"));
panel.add(new StyledLabel(trans.get("lbl.selectTour"), Style.BOLD), "spanx, wrap rel");
}
}
});
- panel.add(new JScrollPane(tourList), "grow, gapright unrel, w 200lp, h 150lp");
+ panel.add(new JScrollPane(tourList), "grow, gapright unrel, w 200lp, h 250lp");
+
+
-
-
// Sub-panel containing description and start button
JPanel sub = new JPanel(new MigLayout("fill, ins 0"));
sub.add(new StyledLabel(trans.get("lbl.description"), -1), "wrap rel");
});
sub.add(start, "growx");
- panel.add(sub, "grow, wrap para, w 200lp, h 150lp");
+ panel.add(sub, "grow, wrap para, w 350lp, h 250lp");
+
+
-
-
JButton close = new JButton(trans.get("button.close"));
close.addActionListener(new ActionListener() {
@Override
this.add(panel);
GUIUtil.setDisposableDialogOptions(this, close);
+ GUIUtil.rememberWindowPosition(this);
tourList.setSelectedIndex(0);
}
}
-
-
+
+
}
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class Slide {
+ private static final String NO_IMAGE = "none";
private final String imageFile;
private SoftReference<BufferedImage> imageReference = null;
private final String text;
-
+
public Slide(String imageFile, String text) {
this.imageFile = imageFile;
this.text = text;
}
-
+
public BufferedImage getImage() {
+ if (imageFile.equals(NO_IMAGE)) {
+ return new BufferedImage(0, 0, BufferedImage.TYPE_INT_ARGB);
+ }
+
// Check the cache
if (imageReference != null) {
BufferedImage image = imageReference.get();
}
-
+
private BufferedImage loadImage() {
BufferedImage img;
import javax.swing.text.html.StyleSheet;
+import net.sf.openrocket.util.BugException;
+
/**
* A manager that loads a number of slide sets from a defined base directory
* and provides access to them.
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class SlideSetManager {
+ private static final String TOURS_BASE_DIR = "datafiles/tours";
private static final String TOURS_FILE = "tours.txt";
private static final String STYLESHEET_FILE = "style.css";
-
+ private static SlideSetManager slideSetManager = null;
+
+
private final String baseDir;
private final Map<String, SlideSet> slideSets = new LinkedHashMap<String, SlideSet>();
List<String> tours = loadTourList();
StyleSheet styleSheet = loadStyleSheet();
- for (String file : tours) {
+ for (String fileAndDir : tours) {
+ String base;
+ String file;
- String base = baseDir + file;
- int index = base.lastIndexOf('/');
+ String fullFileAndDir = baseDir + fileAndDir;
+ int index = fullFileAndDir.lastIndexOf('/');
if (index >= 0) {
- base = base.substring(0, index);
+ base = fullFileAndDir.substring(0, index);
+ file = fullFileAndDir.substring(index + 1);
} else {
base = "";
+ file = "";
}
SlideSetLoader loader = new SlideSetLoader(base);
SlideSet set = loader.load(file);
set.setStyleSheet(styleSheet);
- slideSets.put(file, set);
+ slideSets.put(fileAndDir, set);
}
}
}
+
+
+ /**
+ * Return a singleton implementation that has loaded the default tours.
+ */
+ public static SlideSetManager getSlideSetManager() {
+ if (slideSetManager == null) {
+ try {
+ SlideSetManager ssm = new SlideSetManager(TOURS_BASE_DIR);
+ ssm.load();
+
+ if (ssm.getSlideSetNames().isEmpty()) {
+ throw new FileNotFoundException("No tours found.");
+ }
+
+ slideSetManager = ssm;
+ } catch (IOException e) {
+ throw new BugException(e);
+ }
+ }
+ return slideSetManager;
+ }
}
*/
public class SlideShowComponent extends JSplitPane {
+ private final int WIDTH = 600;
+ private final int HEIGHT_IMAGE = 400;
+ private final int HEIGHT_TEXT = 100;
+
private final ImageDisplayComponent imageDisplay;
private final JEditorPane textPane;
super(VERTICAL_SPLIT);
imageDisplay = new ImageDisplayComponent();
- imageDisplay.setPreferredSize(new Dimension(600, 350));
+ imageDisplay.setPreferredSize(new Dimension(WIDTH, HEIGHT_IMAGE));
this.setLeftComponent(imageDisplay);
textPane = new JEditorPane("text/html", "");
textPane.setEditable(false);
- textPane.setPreferredSize(new Dimension(600, 100));
+ textPane.setPreferredSize(new Dimension(WIDTH, HEIGHT_TEXT));
JScrollPane scrollPanel = new JScrollPane(textPane);
this.setRightComponent(scrollPanel);
- this.setResizeWeight(0.7);
+ this.setResizeWeight(((double) HEIGHT_IMAGE) / (HEIGHT_IMAGE + HEIGHT_TEXT));
}
-
+
public void setSlide(Slide slide) {
this.imageDisplay.setImage(slide.getImage());
this.textPane.setText(slide.getText());
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import java.util.Locale;
+import java.awt.event.KeyEvent;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
import javax.swing.JButton;
+import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JPanel;
-import javax.swing.SwingUtilities;
-import javax.swing.event.HyperlinkEvent;
-import javax.swing.event.HyperlinkListener;
+import javax.swing.JRootPane;
+import javax.swing.KeyStroke;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Chars;
public class SlideShowDialog extends JDialog {
+ private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
private SlideShowComponent slideShowComponent;
JPanel panel = new JPanel(new MigLayout("fill"));
slideShowComponent = new SlideShowComponent();
+ slideShowComponent.addHyperlinkListener(new SlideShowLinkListener(parent));
panel.add(slideShowComponent, "spanx, grow, wrap para");
-
+
JPanel sub = new JPanel(new MigLayout("ins 0, fill"));
prevButton = new JButton(Chars.LEFT_ARROW + " " + trans.get("btn.prev"));
prevButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
+ log.user("Clicked previous button");
setPosition(position - 1);
}
});
sub.add(prevButton, "left");
-
-
+
+
nextButton = new JButton(trans.get("btn.next") + " " + Chars.RIGHT_ARROW);
nextButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
+ log.user("Clicked next button");
setPosition(position + 1);
}
});
sub.add(nextButton, "left, gapleft para");
-
+
sub.add(new JPanel(), "growx");
-
+
closeButton = new JButton(trans.get("button.close"));
closeButton.addActionListener(new ActionListener() {
@Override
});
sub.add(closeButton, "right");
-
+
panel.add(sub, "growx");
this.add(panel);
updateEnabled();
+ addKeyActions();
GUIUtil.setDisposableDialogOptions(this, nextButton);
+ nextButton.grabFocus();
+ GUIUtil.rememberWindowPosition(this);
+ GUIUtil.rememberWindowSize(this);
this.setAlwaysOnTop(true);
}
}
- public static void main(String[] args) throws Exception {
+
+
+
+ private void addKeyActions() {
+ Action next = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ log.user("Key action for next slide");
+ if (position < slideSet.getSlideCount() - 1) {
+ setPosition(position + 1);
+ }
+ }
+ };
- Locale.setDefault(new Locale("de", "DE", ""));
+ Action previous = new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ log.user("Key action for previous slide");
+ if (position > 0) {
+ setPosition(position - 1);
+ }
+ }
+ };
- SlideSetManager manager = new SlideSetManager("datafiles/tours");
- manager.load();
+ String nextKey = "slide:next";
+ String prevKey = "slide:previous";
- final SlideSet set = manager.getSlideSet("test.tour");
+ JRootPane root = this.getRootPane();
+ root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), nextKey);
+ root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, 0), nextKey);
+ root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), prevKey);
+ root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, 0), prevKey);
- SwingUtilities.invokeAndWait(new Runnable() {
- @Override
- public void run() {
-
- SlideShowDialog ssd = new SlideShowDialog(null);
-
- ssd.slideShowComponent.addHyperlinkListener(new HyperlinkListener() {
- @Override
- public void hyperlinkUpdate(HyperlinkEvent e) {
- System.out.println("Hyperlink event: " + e);
- System.out.println("Event type: " + e.getEventType());
- System.out.println("Description: " + e.getDescription());
- System.out.println("URL: " + e.getURL());
- System.out.println("Source element: " + e.getSourceElement());
-
- }
- });
-
- ssd.setSize(500, 500);
- ssd.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
- ssd.setVisible(true);
-
- ssd.setSlideSet(set, 0);
- }
- });
+ root.getActionMap().put(nextKey, next);
+ root.getActionMap().put(prevKey, previous);
}
-
+
+
}
--- /dev/null
+package net.sf.openrocket.gui.help.tours;
+
+import java.awt.Desktop;
+import java.awt.Window;
+import java.net.URL;
+
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkEvent.EventType;
+import javax.swing.event.HyperlinkListener;
+
+import net.sf.openrocket.startup.Application;
+
+public class SlideShowLinkListener implements HyperlinkListener {
+
+ private final Window parent;
+
+ public SlideShowLinkListener(Window parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public void hyperlinkUpdate(HyperlinkEvent event) {
+
+ if (event.getEventType() != EventType.ACTIVATED) {
+ return;
+ }
+
+ URL url = event.getURL();
+ if (url != null && (url.getProtocol().equalsIgnoreCase("http") || url.getProtocol().equals("https"))) {
+
+ if (Desktop.isDesktopSupported()) {
+ try {
+ Desktop.getDesktop().browse(url.toURI());
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+
+ } else {
+
+ String name = event.getDescription();
+ try {
+ SlideSet ss = SlideSetManager.getSlideSetManager().getSlideSet(name);
+
+ SlideShowDialog dialog = new SlideShowDialog(parent);
+ dialog.setSlideSet(ss, 0);
+ dialog.setVisible(true);
+ } catch (IllegalArgumentException e) {
+ Application.getExceptionHandler().handleErrorCondition("Guided tour '" + name + "' not found.");
+ }
+
+ }
+
+ }
+}
}
-
-
+
+
/**
* Set suitable options for a single-use disposable dialog. This includes
* setting ESC to close the dialog, adding the appropriate window icons and
}
-
+
/**
* Add the correct action to close a JDialog when the ESC key is pressed.
* The dialog is closed by sending is a WINDOW_CLOSING event.
}
-
+
/**
* Change the behavior of a component so that TAB and Shift-TAB cycles the focus of
* the components. This is necessary for e.g. <code>JTextArea</code>.
}
-
+
/**
* Set the OpenRocket icons to the window icons.
*
}
-
+
/**
* Set the best available look-and-feel into use.
*/
}
-
+
/**
* Automatically remember the size of a window. This stores the window size in the user
* preferences when resizing/maximizing the window and sets the state on the first call.
}
-
+
/**
* Traverses recursively the component tree, and sets all applicable component
* models to null, so as to remove the listener connections. After calling this
}
-
+
/**
* A mouse listener that toggles the state of a boolean value in a table model
* when clicked on another column of the table.