/* HelpDialog.java created 2007-11-14
*
*/
package org.signalml.app.view.common.dialogs;
import static org.signalml.app.util.i18n.SvarogI18n._;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.net.URL;
import java.util.Stack;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.border.EmptyBorder;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.HyperlinkEvent.EventType;
import javax.swing.text.Document;
import org.signalml.app.util.IconUtils;
import org.signalml.plugin.export.SignalMLException;
import org.springframework.core.io.ClassPathResource;
/**
* Dialog which displays the help for Svarog.
* Contains two elements:
* <ul>
* <li>the web-browser style tool bar with 4 buttons ({@link BackAction back},
* {@link ForwardAction forward}, {@link ReloadAction reload} and
* {@link HomeAction home}),</li>
* <li>the {@link #getScrollPane() scroll pane} with the {@link
* #getHelpPane() help pane}.</li>
* </ul>
* User can use this dialog in the way similar to the web browser.
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
public class HelpDialog extends AbstractDialog {
private static final long serialVersionUID = 1L;
/**
* the text pane in which the actual help is displayed
*/
private JTextPane helpPane;
/**
* the scroll pane which contains {@link #helpPane}
*/
private JScrollPane scrollPane;
/**
* the stack of URLs to that can be used to go to the previously visited
* pages (as back in web browser)
*/
private Stack<URL> backURLs = new Stack<URL>();
/**
* the stack of URLs to that can be used to go to pages from which we went
* back (as forward in web browser)
*/
private Stack<URL> forwardURLs = new Stack<URL>();
/**
* the URL to the page that is currently displayed
*/
private URL currentURL = null;
/**
* the URL to the contents of the help
*/
private URL helpContentsURL = null;
/**
* the {@link ReloadAction action} which reloads the current help page
*/
private ReloadAction reloadAction;
/**
* the {@link HomeAction action} which shows the contents of the help
*/
private HomeAction homeAction;
/**
* the {@link BackAction action} which shows the previously visited page
*/
private BackAction backAction;
/**
* the {@link ForwardAction action} which shows the page from which we went
* back
*/
private ForwardAction forwardAction;
/**
* Constructor. Sets parent window and if this dialog
* blocks top-level windows.
* @param w the parent window or null if there is no parent
* @param isModal true, dialog blocks top-level windows, false otherwise
*/
public HelpDialog(Window w, boolean isModal) {
super(w, isModal);
}
/**
* Initializes this panel:
* <ul>
* <li>sets the icon and the title,</li>
* <li>adds the {@code HyperlinkListener} to the {@link #getHelpPane()
* help panel} (the listener {@link #setPage(URL) changes} the page when
* the link is clicked.</li>
* </ul>
*/
@Override
protected void initialize() {
setTitle(_("Help for signalml"));
setIconImage(IconUtils.loadClassPathImage("org/signalml/app/icon/help.png"));
super.initialize();
getHelpPane().addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType() == EventType.ACTIVATED) {
logger.debug("link activated: [" + e.getURL() + "]");
try {
setPage(e.getURL());
} catch (SignalMLException ex) {
logger.error("Failed to display URL [" + e.getURL().toString() + "]");
}
}
}
});
}
/**
* Creates the interface for this dialog:
* <ul>
* <li>the tool bar with 4 buttons ({@link BackAction back},
* {@link ForwardAction forward}, {@link ReloadAction reload} and
* {@link HomeAction home}),</li>
* <li>the {@link #getScrollPane() scroll pane} with the {@link
* #getHelpPane() help pane}.</li>
* </ul>
*/
@Override
public JComponent createInterface() {
reloadAction = new ReloadAction();
homeAction = new HomeAction();
backAction = new BackAction();
forwardAction = new ForwardAction();
setActionsEnabled();
JToolBar toolBar = new JToolBar();
toolBar.setFloatable(false);
toolBar.add(backAction);
toolBar.add(forwardAction);
toolBar.addSeparator();
toolBar.add(reloadAction);
toolBar.addSeparator();
toolBar.add(Box.createHorizontalGlue());
toolBar.add(homeAction);
JPanel interfacePanel = new JPanel(new BorderLayout());
interfacePanel.setBorder(new EmptyBorder(1,1,1,1));
interfacePanel.add(toolBar, BorderLayout.NORTH);
interfacePanel.add(getScrollPane(), BorderLayout.CENTER);
return interfacePanel;
}
/**
* Returns the text pane in which the actual help is displayed.
* If the pane doesn't exist it is created.
* @return the text pane in which the actual help is displayed
*/
private JTextPane getHelpPane() {
if (helpPane == null) {
helpPane = new JTextPane();
helpPane.setEditable(false);
}
return helpPane;
}
/**
* Returns the scroll pane which contains the {@link #getHelpPane() help
* pane}.
* If the pane doesn't exist it is created.
* @return the scroll pane which contains the help pane
*/
private JScrollPane getScrollPane() {
if (scrollPane == null) {
scrollPane = new JScrollPane(getHelpPane());
scrollPane.setPreferredSize(new Dimension(800,600));
}
return scrollPane;
}
/**
* Returns the URL to the contents of the help.
* If the URL doens't exist it is set to the default location.
* @return the URL to the contents of the help
* @throws SignalMLException if IO exception occurs
*/
public URL getHomeURL() throws SignalMLException {
if (helpContentsURL == null) {
try {
helpContentsURL = (new ClassPathResource("org/signalml/help/contents.html")).getURL();
} catch (IOException ex) {
logger.error("Failed to get help contents", ex);
throw new SignalMLException(ex);
}
}
return helpContentsURL;
}
/**
* Changes the page to the specified URL.
* @param url the URL to the page
* @throws SignalMLException for a null or invalid page specification,
* or exception from the stream being read
* @see JTextPane#setPage(URL)
*/
private void setPageInternal(URL url) throws SignalMLException {
JTextPane helpPane = getHelpPane();
try {
helpPane.setPage(url);
} catch (IOException ex) {
logger.error("Failed to display URL [" + url.toString() + "]");
throw new SignalMLException(ex);
}
}
/**
* Changes the displayed page to the specified URL.
* If no URL is provided the URL to the contents of the help is used.
* <p>
* Performs operations necessary when the page is chagned:
* <ul>
* <li>adds the current URL to the stack of closed pages,</li>
* <li>clears the stack of 'forward' pages,</li>
* <li>{@link #setActionsEnabled() sets} which buttons should be
* active.</li></ul>
* @param url the URL to the page
* @throws SignalMLException for a null or invalid page specification,
* or exception from the stream being read
*/
public void setPage(URL url) throws SignalMLException {
URL targetURL = (url != null ? url : getHomeURL());
setPageInternal(targetURL);
if (currentURL != null) {
backURLs.push(currentURL);
}
forwardURLs.clear();
currentURL = targetURL;
setActionsEnabled();
}
/**
* Clears the stacks of back and forward pages and {@link
* #setActionsEnabled() sets} the state of buttons according to it.
*/
public void reset() {
backURLs.clear();
forwardURLs.clear();
if (isInitialized()) {
setActionsEnabled();
}
}
/**
* Changes the currently displayed page to the previously visited one
* (from the 'back' stack):
* <ul>
* <li>if there is no URL in this stack does nothing, otherwise</li>
* <li>adds the current URL to the stack of 'forward' pages,</li>
* <li>{@link #setActionsEnabled() sets} which buttons should be
* active.</li>
* </ul>
* @throws SignalMLException for a null or invalid page specification,
* or exception from the stream being read
*/
public void back() throws SignalMLException {
if (backURLs.isEmpty()) {
return;
}
URL targetURL = backURLs.pop();
setPageInternal(targetURL);
if (currentURL != null) {
forwardURLs.push(currentURL);
}
currentURL = targetURL;
setActionsEnabled();
}
/**
* Changes the currently displayed page to the page from which the user
* went back (first from the 'forward' stack):
* <ul>
* <li>if there is no URL in this stack does nothing, otherwise</li>
* <li>adds the current URL to the stack of 'back' pages,</li>
* <li>{@link #setActionsEnabled() sets} which buttons should be
* active.</li>
* </ul>
* @throws SignalMLException for a null or invalid page specification,
* or exception from the stream being read
*/
public void forward() throws SignalMLException {
if (forwardURLs.isEmpty()) {
return;
}
URL targetURL = forwardURLs.pop();
setPageInternal(targetURL);
if (currentURL != null) {
backURLs.push(currentURL);
}
currentURL = targetURL;
setActionsEnabled();
}
/**
* Reloads the current page. If there is no page does nothing.
* @throws SignalMLException for invalid page specification,
* or exception from the stream being read
*/
public void reload() throws SignalMLException {
if (currentURL == null) {
return;
}
// this forces reload
Document doc = getHelpPane().getDocument();
doc.putProperty(Document.StreamDescriptionProperty, null);
setPageInternal(currentURL);
}
/**
* Sets if the following actions should be enabled:
* <ul>
* <li>the {@link ReloadAction#setEnabled() reload action},</li>
* <li>the {@link BackAction#setEnabled() back action},</li>
* <li>the {@link ForwardAction#setEnabled() forward action}.</li>
* </ul>
*/
public void setActionsEnabled() {
reloadAction.setEnabled();
backAction.setEnabled();
forwardAction.setEnabled();
}
/**
* {@link #setPage(URL) Sets} the current page to the URL provided as
* a model.
*/
@Override
public void fillDialogFromModel(Object model) throws SignalMLException {
setPage((URL) model);
}
/**
* Does nothing, beacuse it is a read only dialog.
*/
@Override
public void fillModelFromDialog(Object model) throws SignalMLException {
// do nothing
}
/**
* The model for this dialog must be either null or of type URL.
*/
@Override
public boolean supportsModelClass(Class<?> clazz) {
return (clazz == null || URL.class.isAssignableFrom(clazz));
}
/**
* This dialog can not be canceled (has no CANCEL) button.
*/
@Override
public boolean isCancellable() {
return false;
}
/**
* Action which {@link HelpDialog#reload() reloads} the current page.
*/
protected class ReloadAction extends AbstractAction {
private static final long serialVersionUID = 1L;
/**
* Constructor. Sets the icon and the tool tip.
*/
public ReloadAction() {
super();
putValue(AbstractAction.SMALL_ICON, IconUtils.loadClassPathIcon("org/signalml/app/icon/reload.png"));
putValue(AbstractAction.SHORT_DESCRIPTION,_("Reload current page"));
}
/**
* When the action is performed the current page is
* {@link HelpDialog#reload() reloaded}
*/
public void actionPerformed(ActionEvent ev) {
try {
reload();
} catch (SignalMLException ex) {
logger.error("Failed to reload", ex);
}
}
/**
* If the current URL is different the null this action is enabled,
* otherwise it is disabled.
*/
public void setEnabled() {
setEnabled(currentURL != null);
}
}
/**
* The action which changes the current page to the home page (contents of
* the help).
*/
protected class HomeAction extends AbstractAction {
private static final long serialVersionUID = 1L;
/**
* Constructor. Sets the icon and the tool tip.
*/
public HomeAction() {
super();
putValue(AbstractAction.SMALL_ICON, IconUtils.loadClassPathIcon("org/signalml/app/icon/help.png"));
putValue(AbstractAction.SHORT_DESCRIPTION,_("Help contents"));
}
/**
* When this action is performed the current page is changed to the
* home page.
*/
public void actionPerformed(ActionEvent ev) {
try {
setPage(null);
} catch (SignalMLException ex) {
logger.error("Failed to go to contents", ex);
}
}
}
/**
* The action which changes the current page to the previously visited page
* (the first page from 'back' stack).
*/
protected class BackAction extends AbstractAction {
private static final long serialVersionUID = 1L;
/**
* Constructor. Sets the icon and the tool tip.
*/
public BackAction() {
super();
putValue(AbstractAction.SMALL_ICON, IconUtils.loadClassPathIcon("org/signalml/app/icon/back.png"));
putValue(AbstractAction.SHORT_DESCRIPTION,_("Previous topic"));
}
/**
* When this action is performed the {@link HelpDialog#back()} function
* is called.
*/
public void actionPerformed(ActionEvent ev) {
try {
back();
} catch (SignalMLException ex) {
logger.error("Failed to go back", ex);
}
}
/**
* If there is at least one URL in the 'back' stack enables this action,
* otherwise disables it.
*/
public void setEnabled() {
setEnabled(!backURLs.isEmpty());
}
}
/**
* The action which changes the current page to the page from which te user
* went back (the first page from 'forward' stack).
*/
protected class ForwardAction extends AbstractAction {
private static final long serialVersionUID = 1L;
/**
* Constructor. Sets the icon and the tool tip.
*/
public ForwardAction() {
super();
putValue(AbstractAction.SMALL_ICON, IconUtils.loadClassPathIcon("org/signalml/app/icon/forward.png"));
putValue(AbstractAction.SHORT_DESCRIPTION,_("Next topic"));
}
/**
* When this action is performed the {@link HelpDialog#forward()} function
* is called.
*/
public void actionPerformed(ActionEvent ev) {
try {
forward();
} catch (SignalMLException ex) {
logger.error("Failed to go forward", ex);
}
}
/**
* If there is at least one URL in the 'forward' stack enables this
* action, otherwise disables it.
*/
public void setEnabled() {
setEnabled(!forwardURLs.isEmpty());
}
}
}