guided tours implementation
This commit is contained in:
parent
e3d9e41ba3
commit
73e8644aca
BIN
datafiles/tours/designed_rocket.png
Normal file
BIN
datafiles/tours/designed_rocket.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
BIN
datafiles/tours/left_design.png
Normal file
BIN
datafiles/tours/left_design.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
datafiles/tours/main_window.png
Normal file
BIN
datafiles/tours/main_window.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
BIN
datafiles/tours/rocket_configuration.png
Normal file
BIN
datafiles/tours/rocket_configuration.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
15
datafiles/tours/style.css
Normal file
15
datafiles/tours/style.css
Normal 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
18
datafiles/tours/test.tour
Normal 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.
|
||||
|
16
datafiles/tours/test2.tour
Normal file
16
datafiles/tours/test2.tour
Normal 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.
|
||||
|
6
datafiles/tours/test_de.tour
Normal file
6
datafiles/tours/test_de.tour
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
Das test Tour
|
||||
|
||||
[left_design.png]
|
||||
|
||||
Das is ein test tour.
|
6
datafiles/tours/tours.txt
Normal file
6
datafiles/tours/tours.txt
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
# This file lists all the available tours.
|
||||
|
||||
test.tour
|
||||
test2.tour
|
||||
|
@ -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!
|
||||
|
||||
|
||||
|
124
src/net/sf/openrocket/gui/components/ImageDisplayComponent.java
Normal file
124
src/net/sf/openrocket/gui/components/ImageDisplayComponent.java
Normal 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);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
73
src/net/sf/openrocket/gui/help/tours/Slide.java
Normal file
73
src/net/sf/openrocket/gui/help/tours/Slide.java
Normal 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;
|
||||
}
|
||||
}
|
62
src/net/sf/openrocket/gui/help/tours/SlideSet.java
Normal file
62
src/net/sf/openrocket/gui/help/tours/SlideSet.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
173
src/net/sf/openrocket/gui/help/tours/SlideSetLoader.java
Normal file
173
src/net/sf/openrocket/gui/help/tours/SlideSetLoader.java
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
134
src/net/sf/openrocket/gui/help/tours/SlideSetManager.java
Normal file
134
src/net/sf/openrocket/gui/help/tours/SlideSetManager.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
72
src/net/sf/openrocket/gui/help/tours/SlideShowComponent.java
Normal file
72
src/net/sf/openrocket/gui/help/tours/SlideShowComponent.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
160
src/net/sf/openrocket/gui/help/tours/SlideShowDialog.java
Normal file
160
src/net/sf/openrocket/gui/help/tours/SlideShowDialog.java
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
120
src/net/sf/openrocket/gui/help/tours/TextLineReader.java
Normal file
120
src/net/sf/openrocket/gui/help/tours/TextLineReader.java
Normal 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");
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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';
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user