guided tours implementation

This commit is contained in:
Sampo Niskanen 2011-12-19 05:00:30 +00:00
parent e3d9e41ba3
commit 73e8644aca
21 changed files with 1212 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

15
datafiles/tours/style.css Normal file
View File

@ -0,0 +1,15 @@
/*
* 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;
}

18
datafiles/tours/test.tour Normal file
View File

@ -0,0 +1,18 @@
# 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.

View File

@ -0,0 +1,16 @@
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.

View File

@ -0,0 +1,6 @@
Das test Tour
[left_design.png]
Das is ein test tour.

View File

@ -0,0 +1,6 @@
# This file lists all the available tours.
test.tour
test2.tour

View File

@ -999,6 +999,8 @@ main.menu.analyze.optimization.desc = General rocket design optimization
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
@ -1531,3 +1533,14 @@ CompassSelectionButton.lbl.SW = SW
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!

View File

@ -0,0 +1,124 @@
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);
}
});
}
}

View File

@ -0,0 +1,197 @@
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();
}
}
}

View File

@ -0,0 +1,73 @@
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;
}
}

View File

@ -0,0 +1,62 @@
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;
}
}

View File

@ -0,0 +1,173 @@
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();
}
}

View File

@ -0,0 +1,134 @@
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();
}
}
}

View File

@ -0,0 +1,72 @@
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);
}
}

View File

@ -0,0 +1,160 @@
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);
}
});
}
}

View File

@ -0,0 +1,120 @@
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");
}
}

View File

@ -81,14 +81,15 @@ import net.sf.openrocket.gui.dialogs.SwingWorkerDialog;
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;
@ -672,6 +673,24 @@ public class BasicFrame extends JFrame {
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);

View File

@ -35,6 +35,9 @@ public class Chars {
/** 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';