--- /dev/null
+
+/*
+ * This stylesheet is used to style all guided tours.
+ * Use only simple styling.
+ *
+ * NOTE: The Sun JRE does not support "em" widths, use only pixels.
+ */
+
+div.base {
+ font-family: Arial, sans-serif;
+}
+
+p {
+ margin: 0 0 5px;
+}
--- /dev/null
+
+# Foo bar
+
+A test tour
+
+This is the <i>description</i>.
+
+[left_design.png]
+
+This is the left_design file.
+
+It's nifty. It's also <a href="foobar">economical</a>.
+
+
+
+[main_window.png]
+This is the next one.
+
--- /dev/null
+Another test tour
+
+<p>This is the second tour.
+<p>You get it?
+
+[left_design.png]
+
+This is the second tour.
+
+It's nifty. It's also <a href="foobar">economical</a>.
+
+
+
+[main_window.png]
+This is the next one.
+
--- /dev/null
+
+Das test Tour
+
+[left_design.png]
+
+Das is ein test tour.
--- /dev/null
+
+# This file lists all the available tours.
+
+test.tour
+test2.tour
+
main.menu.help = Help
main.menu.help.desc = Information about OpenRocket
+main.menu.help.tours = Guided tours
+main.menu.help.tours.desc = Take guided tours on OpenRocket
main.menu.help.license = License
main.menu.help.license.desc = OpenRocket license information
main.menu.help.bugReport = Bug report
CompassSelectionButton.lbl.W = W
CompassSelectionButton.lbl.NW = NW
+
+SlideShowDialog.btn.next = Next
+SlideShowDialog.btn.prev = Previous
+
+GuidedTourSelectionDialog.title = Guided tours
+GuidedTourSelectionDialog.lbl.selectTour = Select guided tour:
+GuidedTourSelectionDialog.lbl.description = Tour description:
+GuidedTourSelectionDialog.lbl.length = Number of slides:
+GuidedTourSelectionDialog.btn.start = Start tour!
+
+
--- /dev/null
+package net.sf.openrocket.gui.components;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.File;
+
+import javax.imageio.ImageIO;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.util.MathUtil;
+
+/**
+ * Draws a BufferedImage centered and scaled to fit to the component.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ImageDisplayComponent extends JPanel {
+
+ private BufferedImage image;
+
+ public ImageDisplayComponent() {
+ this(null);
+ }
+
+ public ImageDisplayComponent(BufferedImage image) {
+ this.image = image;
+ }
+
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ if (image == null) {
+ return;
+ }
+
+ final int width = Math.max(this.getWidth(), 1);
+ final int height = Math.max(this.getHeight(), 1);
+
+ final int origWidth = Math.max(image.getWidth(), 1);
+ final int origHeight = Math.max(image.getHeight(), 1);
+
+
+ // Determine scaling factor
+ double scaleX = ((double) width) / origWidth;
+ double scaleY = ((double) height) / origHeight;
+
+ double scale = MathUtil.min(scaleX, scaleY);
+
+ if (scale >= 1) {
+ scale = 1.0;
+ }
+
+
+ // Center in the middle of the component
+ int finalWidth = (int) Math.round(origWidth * scale);
+ int finalHeight = (int) Math.round(origHeight * scale);
+
+ int posX = (width - finalWidth) / 2;
+ int posY = (height - finalHeight) / 2;
+
+
+ // Draw the image
+ int dx1 = posX;
+ int dy1 = posY;
+ int dx2 = posX + finalWidth;
+ int dy2 = posY + finalHeight;
+ int sx1 = 0;
+ int sy1 = 0;
+ int sx2 = origWidth;
+ int sy2 = origHeight;
+
+ Graphics2D g2 = (Graphics2D) g;
+ g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
+ g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2.drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null);
+
+ }
+
+
+ public BufferedImage getImage() {
+ return image;
+ }
+
+
+ public void setImage(BufferedImage image) {
+ this.image = image;
+ this.repaint();
+ }
+
+
+ public static void main(String[] args) throws Exception {
+ final BufferedImage image = ImageIO.read(new File("test.png"));
+
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+
+ JFrame frame = new JFrame();
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+ panel.setBackground(Color.red);
+ frame.add(panel);
+
+ ImageDisplayComponent c = new ImageDisplayComponent(image);
+ panel.add(c, "grow");
+
+ frame.setSize(500, 500);
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.setVisible(true);
+
+ }
+ });
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.gui.help.tours;
+
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+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 javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JEditorPane;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.StyleSheet;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.gui.components.StyledLabel.Style;
+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;
+
+ private SlideShowDialog slideShowDialog;
+
+ private JList tourList;
+ private JEditorPane tourDescription;
+ private JLabel tourLength;
+
+
+ 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);
+ }
+
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+
+ panel.add(new StyledLabel(trans.get("lbl.selectTour"), Style.BOLD), "spanx, wrap rel");
+
+ tourList = new JList(new TourListModel());
+ tourList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ tourList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ updateText();
+ }
+ });
+ tourList.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ startTour();
+ }
+ }
+ });
+ panel.add(new JScrollPane(tourList), "grow, gapright unrel, w 200lp, h 150lp");
+
+
+
+ // 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");
+
+ tourDescription = new JEditorPane("text/html", "");
+ tourDescription.setEditable(false);
+ StyleSheet ss = slideSetManager.getSlideSet(tourNames.get(0)).getStyleSheet();
+ ((HTMLDocument) tourDescription.getDocument()).getStyleSheet().addStyleSheet(ss);
+ sub.add(new JScrollPane(tourDescription), "grow, wrap rel");
+
+ tourLength = new StyledLabel(-1);
+ sub.add(tourLength, "wrap unrel");
+
+ JButton start = new JButton(trans.get("btn.start"));
+ start.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ startTour();
+ }
+ });
+ sub.add(start, "growx");
+
+ panel.add(sub, "grow, wrap para, w 200lp, h 150lp");
+
+
+
+ JButton close = new JButton(trans.get("button.close"));
+ close.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ GuidedTourSelectionDialog.this.dispose();
+ }
+ });
+ panel.add(close, "spanx, right");
+
+ this.add(panel);
+ GUIUtil.setDisposableDialogOptions(this, close);
+ tourList.setSelectedIndex(0);
+ }
+
+
+ private void startTour() {
+ SlideSet ss = getSelectedSlideSet();
+ if (ss == null) {
+ return;
+ }
+
+ if (slideShowDialog != null && !slideShowDialog.isVisible()) {
+ closeTour();
+ }
+
+ if (slideShowDialog == null) {
+ slideShowDialog = new SlideShowDialog(this);
+ }
+
+ slideShowDialog.setSlideSet(ss, 0);
+ slideShowDialog.setVisible(true);
+ }
+
+
+ private void closeTour() {
+ if (slideShowDialog != null) {
+ slideShowDialog.dispose();
+ slideShowDialog = null;
+ }
+ }
+
+
+ private void updateText() {
+ SlideSet ss = getSelectedSlideSet();
+ if (ss != null) {
+ tourDescription.setText(ss.getDescription());
+ tourLength.setText(trans.get("lbl.length") + " " + ss.getSlideCount());
+ } else {
+ tourDescription.setText("");
+ tourLength.setText(trans.get("lbl.length"));
+ }
+ }
+
+
+ @SuppressWarnings("unchecked")
+ private SlideSet getSelectedSlideSet() {
+ return ((Named<SlideSet>) tourList.getSelectedValue()).get();
+ }
+
+ private class TourListModel extends AbstractListModel {
+
+ @Override
+ public Object getElementAt(int index) {
+ String name = tourNames.get(index);
+ SlideSet set = slideSetManager.getSlideSet(name);
+ return new Named<SlideSet>(set, set.getTitle());
+ }
+
+ @Override
+ public int getSize() {
+ return tourNames.size();
+ }
+
+ }
+
+
+
+}
--- /dev/null
+package net.sf.openrocket.gui.help.tours;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.lang.ref.SoftReference;
+import java.net.URL;
+
+import javax.imageio.ImageIO;
+
+/**
+ * An individual slide in a guided tour. It contains a image (or reference to an
+ * image file) plus a text description (in HTML).
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class Slide {
+
+ 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() {
+
+ // Check the cache
+ if (imageReference != null) {
+ BufferedImage image = imageReference.get();
+ if (image != null) {
+ return image;
+ }
+ }
+
+ // Otherwise load and cache
+ BufferedImage image = loadImage();
+ imageReference = new SoftReference<BufferedImage>(image);
+
+ return image;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+
+
+ private BufferedImage loadImage() {
+ BufferedImage img;
+
+ try {
+ URL url = ClassLoader.getSystemResource(imageFile);
+ if (url != null) {
+ img = ImageIO.read(url);
+ } else {
+ //FIXME
+ img = null;
+ }
+ } catch (IOException e) {
+ // FIXME
+ img = null;
+ }
+
+ return img;
+ }
+}
--- /dev/null
+package net.sf.openrocket.gui.help.tours;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.text.html.StyleSheet;
+
+/**
+ * A set of slides that composes a tour.
+ *
+ * A slide set contains a (localized, plain-text) title for the tour, a (possibly
+ * multiline, HTML-formatted) description and a number of slides.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class SlideSet {
+
+ private String title = "";
+ private String description = "";
+ private final List<Slide> slides = new ArrayList<Slide>();
+ private StyleSheet styleSheet = new StyleSheet();
+
+
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String name) {
+ this.title = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+
+ public Slide getSlide(int index) {
+ return this.slides.get(index);
+ }
+
+ public void addSlide(Slide slide) {
+ this.slides.add(slide);
+ }
+
+ public int getSlideCount() {
+ return this.slides.size();
+ }
+
+ public StyleSheet getStyleSheet() {
+ return styleSheet;
+ }
+
+ public void setStyleSheet(StyleSheet styleSheet) {
+ this.styleSheet = styleSheet;
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.gui.help.tours;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import net.sf.openrocket.util.BugException;
+
+/**
+ * Class that loads a slide set from a file.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class SlideSetLoader {
+
+ private static final Pattern NEW_SLIDE_PATTERN = Pattern.compile("^\\[(.*)\\]$");
+
+ private final String baseDir;
+ private TextLineReader source;
+ private Locale locale;
+
+
+
+
+ /**
+ * Constructor.
+ *
+ * @param baseDir The base directory from which to load from. It is prepended to the loaded
+ * file names and image file names.
+ */
+ public SlideSetLoader(String baseDir) {
+ this(baseDir, Locale.getDefault());
+ }
+
+
+ /**
+ * Constructor.
+ *
+ * @param baseDir The base directory from which to load from. It is prepended to the loaded
+ * file names and image file names.
+ * @param locale The locale for which the files are loaded.
+ */
+ public SlideSetLoader(String baseDir, Locale locale) {
+ if (baseDir.length() > 0 && !baseDir.endsWith("/")) {
+ baseDir = baseDir + "/";
+ }
+ this.baseDir = baseDir;
+ this.locale = locale;
+ }
+
+
+ /**
+ * Load a slide set from a file. The base directory is prepended to the
+ * file name first.
+ *
+ * @param filename the file to read in the base directory.
+ * @return the slide set
+ */
+ public SlideSet load(String filename) throws IOException {
+ String file = baseDir + filename;
+ InputStream in = getLocalizedFile(file);
+
+ try {
+ InputStreamReader reader = new InputStreamReader(in, "UTF-8");
+ return load(reader);
+ } finally {
+ in.close();
+ }
+ }
+
+
+ private InputStream getLocalizedFile(String filename) throws IOException {
+ for (String file : generateLocalizedFiles(filename)) {
+ InputStream in = ClassLoader.getSystemResourceAsStream(file);
+ if (in != null) {
+ return in;
+ }
+ }
+ throw new FileNotFoundException("File '" + filename + "' not found.");
+ }
+
+ private List<String> generateLocalizedFiles(String filename) {
+ String base, ext;
+ int index = filename.lastIndexOf('.');
+ if (index >= 0) {
+ base = filename.substring(0, index);
+ ext = filename.substring(index);
+ } else {
+ base = filename;
+ ext = "";
+ }
+
+
+ List<String> list = new ArrayList<String>();
+ list.add(base + "_" + locale.getLanguage() + "_" + locale.getCountry() + "_" + locale.getVariant() + ext);
+ list.add(base + "_" + locale.getLanguage() + "_" + locale.getCountry() + ext);
+ list.add(base + "_" + locale.getLanguage() + ext);
+ list.add(base + ext);
+ return list;
+ }
+
+
+ /**
+ * Load slide set from a reader.
+ *
+ * @param reader the reader to read from.
+ * @return the slide set.
+ */
+ public SlideSet load(Reader reader) throws IOException {
+ source = new TextLineReader(reader);
+
+ // Read title and description
+ String title = source.next();
+ StringBuilder desc = new StringBuilder();
+ while (!nextLineStartsSlide()) {
+ if (desc.length() > 0) {
+ desc.append('\n');
+ }
+ desc.append(source.next());
+ }
+
+ // Create the slide set
+ SlideSet set = new SlideSet();
+ set.setTitle(title);
+ set.setDescription(desc.toString());
+
+
+ // Read the slides
+ while (source.hasNext()) {
+ Slide s = readSlide();
+ set.addSlide(s);
+ }
+
+ return set;
+ }
+
+
+ private Slide readSlide() {
+
+ String imgLine = source.next();
+ Matcher matcher = NEW_SLIDE_PATTERN.matcher(imgLine);
+ if (!matcher.matches()) {
+ throw new BugException("Line did not match new slide pattern: " + imgLine);
+ }
+
+ String imageFile = matcher.group(1);
+
+ StringBuffer desc = new StringBuffer();
+ while (source.hasNext() && !nextLineStartsSlide()) {
+ if (desc.length() > 0) {
+ desc.append('\n');
+ }
+ desc.append(source.next());
+ }
+
+ return new Slide(baseDir + imageFile, desc.toString());
+ }
+
+
+
+ private boolean nextLineStartsSlide() {
+ return NEW_SLIDE_PATTERN.matcher(source.peek()).matches();
+ }
+
+
+}
--- /dev/null
+package net.sf.openrocket.gui.help.tours;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.text.html.StyleSheet;
+
+/**
+ * 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_FILE = "tours.txt";
+ private static final String STYLESHEET_FILE = "style.css";
+
+
+ private final String baseDir;
+ private final Map<String, SlideSet> slideSets = new LinkedHashMap<String, SlideSet>();
+
+
+ /**
+ * Sole constructor.
+ *
+ * @param baseDir the base directory containing the tours and style files.
+ */
+ public SlideSetManager(String baseDir) {
+ if (baseDir.length() > 0 && !baseDir.endsWith("/")) {
+ baseDir = baseDir + "/";
+ }
+ this.baseDir = baseDir;
+ }
+
+
+ /**
+ * Load all the tours.
+ */
+ public void load() throws IOException {
+ slideSets.clear();
+
+ List<String> tours = loadTourList();
+ StyleSheet styleSheet = loadStyleSheet();
+
+ for (String file : tours) {
+
+ String base = baseDir + file;
+ int index = base.lastIndexOf('/');
+ if (index >= 0) {
+ base = base.substring(0, index);
+ } else {
+ base = "";
+ }
+
+ SlideSetLoader loader = new SlideSetLoader(base);
+ SlideSet set = loader.load(file);
+ set.setStyleSheet(styleSheet);
+ slideSets.put(file, set);
+ }
+
+ }
+
+
+ /**
+ * Return a set containing all the slide set names.
+ */
+ public List<String> getSlideSetNames() {
+ return new ArrayList<String>(slideSets.keySet());
+ }
+
+ /**
+ * Retrieve an individual slide set.
+ *
+ * @param name the name of the slide set to retrieve.
+ * @return the slide set (never null)
+ * @throws IllegalArgumentException if the slide set with the name does not exist.
+ */
+ public SlideSet getSlideSet(String name) {
+ SlideSet s = slideSets.get(name);
+ if (s == null) {
+ throw new IllegalArgumentException("Slide set with name '" + name + "' not found.");
+ }
+ return s;
+ }
+
+
+ private List<String> loadTourList() throws IOException {
+ InputStream in = ClassLoader.getSystemResourceAsStream(baseDir + TOURS_FILE);
+ if (in == null) {
+ throw new FileNotFoundException("File '" + baseDir + TOURS_FILE + "' not found.");
+ }
+
+ try {
+
+ List<String> tours = new ArrayList<String>();
+ TextLineReader reader = new TextLineReader(in);
+ while (reader.hasNext()) {
+ tours.add(reader.next());
+ }
+ return tours;
+
+ } finally {
+ in.close();
+ }
+ }
+
+
+ private StyleSheet loadStyleSheet() throws IOException {
+ InputStream in = ClassLoader.getSystemResourceAsStream(baseDir + STYLESHEET_FILE);
+ if (in == null) {
+ throw new FileNotFoundException("File '" + baseDir + STYLESHEET_FILE + "' not found.");
+ }
+
+ try {
+
+ StyleSheet ss = new StyleSheet();
+ InputStreamReader reader = new InputStreamReader(in, "UTF-8");
+ ss.loadRules(reader, null);
+ return ss;
+
+ } finally {
+ in.close();
+ }
+
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.gui.help.tours;
+
+import java.awt.Dimension;
+
+import javax.swing.JEditorPane;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.StyleSheet;
+
+import net.sf.openrocket.gui.components.ImageDisplayComponent;
+
+/**
+ * Component that displays a single slide, with the image on top and
+ * text below it. The portions are resizeable.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class SlideShowComponent extends JSplitPane {
+
+ private final ImageDisplayComponent imageDisplay;
+ private final JEditorPane textPane;
+
+
+ public SlideShowComponent() {
+ super(VERTICAL_SPLIT);
+
+ imageDisplay = new ImageDisplayComponent();
+ imageDisplay.setPreferredSize(new Dimension(600, 350));
+ this.setLeftComponent(imageDisplay);
+
+ textPane = new JEditorPane("text/html", "");
+ textPane.setEditable(false);
+ textPane.setPreferredSize(new Dimension(600, 100));
+
+ JScrollPane scrollPanel = new JScrollPane(textPane);
+ this.setRightComponent(scrollPanel);
+
+ this.setResizeWeight(0.7);
+ }
+
+
+
+ public void setSlide(Slide slide) {
+ this.imageDisplay.setImage(slide.getImage());
+ this.textPane.setText(slide.getText());
+ }
+
+
+ /**
+ * Replace the current HTML style sheet with a new style sheet.
+ */
+ public void setStyleSheet(StyleSheet newStyleSheet) {
+ HTMLDocument doc = (HTMLDocument) textPane.getDocument();
+ StyleSheet base = doc.getStyleSheet();
+ StyleSheet[] linked = base.getStyleSheets();
+ if (linked != null) {
+ for (StyleSheet ss : linked) {
+ base.removeStyleSheet(ss);
+ }
+ }
+
+ base.addStyleSheet(newStyleSheet);
+ }
+
+
+ public void addHyperlinkListener(HyperlinkListener listener) {
+ textPane.addHyperlinkListener(listener);
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.gui.help.tours;
+
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Locale;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+
+import net.miginfocom.swing.MigLayout;
+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.Chars;
+
+public class SlideShowDialog extends JDialog {
+
+ private static final Translator trans = Application.getTranslator();
+
+ private SlideShowComponent slideShowComponent;
+ private SlideSet slideSet;
+ private int position;
+
+ private JButton nextButton;
+ private JButton prevButton;
+ private JButton closeButton;
+
+
+ public SlideShowDialog(Window parent) {
+ super(parent, ModalityType.MODELESS);
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+
+ slideShowComponent = new SlideShowComponent();
+ 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) {
+ 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) {
+ 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
+ public void actionPerformed(ActionEvent e) {
+ SlideShowDialog.this.dispose();
+ }
+ });
+ sub.add(closeButton, "right");
+
+
+ panel.add(sub, "growx");
+
+ this.add(panel);
+ updateEnabled();
+ GUIUtil.setDisposableDialogOptions(this, nextButton);
+ this.setAlwaysOnTop(true);
+ }
+
+ public void setSlideSet(SlideSet slideSet, int position) {
+ this.slideSet = slideSet;
+ this.setTitle(slideSet.getTitle() + " " + Chars.EMDASH + " OpenRocket");
+ slideShowComponent.setStyleSheet(slideSet.getStyleSheet());
+ setPosition(position);
+ }
+
+ public void setPosition(int position) {
+ if (this.slideSet == null) {
+ throw new BugException("setPosition called when slideSet is null");
+ }
+
+ if (position < 0 || position >= slideSet.getSlideCount()) {
+ throw new BugException("position exceeds slide count, position=" + position +
+ " slideCount=" + slideSet.getSlideCount());
+ }
+
+ this.position = position;
+ slideShowComponent.setSlide(slideSet.getSlide(position));
+ updateEnabled();
+ }
+
+
+ private void updateEnabled() {
+ if (slideSet == null) {
+ prevButton.setEnabled(false);
+ nextButton.setEnabled(false);
+ return;
+ }
+
+ prevButton.setEnabled(position > 0);
+ nextButton.setEnabled(position < slideSet.getSlideCount() - 1);
+ }
+
+
+ public static void main(String[] args) throws Exception {
+
+ Locale.setDefault(new Locale("de", "DE", ""));
+
+ SlideSetManager manager = new SlideSetManager("datafiles/tours");
+ manager.load();
+
+ final SlideSet set = manager.getSlideSet("test.tour");
+
+ 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);
+ }
+ });
+ }
+
+
+}
--- /dev/null
+package net.sf.openrocket.gui.help.tours;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import net.sf.openrocket.util.BugException;
+
+/**
+ * Read from a Reader object one line at a time, ignoring blank lines,
+ * preceding and trailing whitespace and comment lines starting with '#'.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class TextLineReader implements Iterator<String> {
+
+ private static final Charset UTF8 = Charset.forName("UTF-8");
+
+
+
+ private final BufferedReader reader;
+
+ private String next = null;
+
+ /**
+ * Read from an input stream with UTF-8 character encoding.
+ */
+ public TextLineReader(InputStream inputStream) {
+ this(new InputStreamReader(inputStream, UTF8));
+ }
+
+
+ /**
+ * Read from a reader.
+ */
+ public TextLineReader(Reader reader) {
+ if (reader instanceof BufferedReader) {
+ this.reader = (BufferedReader) reader;
+ } else {
+ this.reader = new BufferedReader(reader);
+ }
+ }
+
+
+ /**
+ * Test whether the file has more lines available.
+ */
+ @Override
+ public boolean hasNext() {
+ if (next != null) {
+ return true;
+ }
+
+ try {
+ next = readLine();
+ } catch (IOException e) {
+ throw new BugException(e);
+ }
+
+ return next != null;
+ }
+
+
+ /**
+ * Retrieve the next non-blank, non-comment line.
+ */
+ @Override
+ public String next() {
+ if (hasNext()) {
+ String ret = next;
+ next = null;
+ return ret;
+ }
+
+ throw new NoSuchElementException("End of file reached");
+ }
+
+
+ /**
+ * Peek what the next line would be.
+ */
+ public String peek() {
+ if (hasNext()) {
+ return next;
+ }
+
+ throw new NoSuchElementException("End of file reached");
+ }
+
+
+ private String readLine() throws IOException {
+
+ while (true) {
+ // Read the next line
+ String line = reader.readLine();
+ if (line == null) {
+ return null;
+ }
+
+ // Check whether to accept the line
+ line = line.trim();
+ if (line.length() > 0 && line.charAt(0) != '#') {
+ return line;
+ }
+ }
+
+ }
+
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Remove not supported");
+ }
+
+}
import net.sf.openrocket.gui.dialogs.WarningDialog;
import net.sf.openrocket.gui.dialogs.optimization.GeneralOptimizationDialog;
import net.sf.openrocket.gui.dialogs.preferences.PreferencesDialog;
+import net.sf.openrocket.gui.help.tours.GuidedTourSelectionDialog;
import net.sf.openrocket.gui.main.componenttree.ComponentTree;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
import net.sf.openrocket.gui.util.FileHelper;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.gui.util.OpenFileWorker;
-import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.gui.util.SaveFileWorker;
+import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
menubar.add(menu);
+ // Guided tours
+
+ item = new JMenuItem(trans.get("main.menu.help.tours"), KeyEvent.VK_L);
+ // TODO: Icon
+ item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.help.tours.desc"));
+ item.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Guided tours selected");
+ // FIXME: Singleton
+ new GuidedTourSelectionDialog(BasicFrame.this).setVisible(true);
+ }
+ });
+ menu.add(item);
+
+ menu.addSeparator();
+
+
//// License
item = new JMenuItem(trans.get("main.menu.help.license"), KeyEvent.VK_L);
item.setIcon(Icons.HELP_LICENSE);
/** Zero-width space */
public static final char ZWSP = '\u200B';
+ /** Em dash */
+ public static final char EMDASH = '\u2014';
+
/** Micro sign (Greek letter mu) */
public static final char MICRO = '\u00B5';