package chatty.gui.components.help; import chatty.Chatty; import chatty.gui.UrlOpener; import chatty.util.WrapHistory; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.util.logging.Logger; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.JToolBar; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.text.Document; /** * Simple Frame that shows a HTML page as About/Help. * * @author tduva */ public class About extends JFrame implements ActionListener { private static final Logger LOGGER = Logger.getLogger(About.class.getName()); private static final String BASE_HELP_URL = Chatty.WEBSITE+"/help/"; private static final String DEFAULT_PAGE = "help.html"; private final JButton historyBackButton; private final JButton historyForwardButton; private final JTextField currentLocation; private final JTextPane textPane = new JTextPane(); private String reference; private String currentPage = ""; private String currentReference = ""; private final WrapHistory<HistoryItem> history = new WrapHistory<>(20); public About() { setTitle("About/Help - Chatty"); // Text pane JScrollPane scroll = new JScrollPane(textPane); scroll.getVerticalScrollBar().setUnitIncrement(40); textPane.setEditable(false); textPane.addHyperlinkListener(new HyperlinkListener() { @Override public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { // Jump to another place in the document String url = e.getURL().toString(); String protocol = e.getURL().getProtocol(); if (protocol.equals("http") || protocol.equals("https")) { UrlOpener.openUrlPrompt(About.this, url, true); } else if (protocol.equals("file") || protocol.equals("jar")) { String path = e.getURL().getFile(); String file = path.substring(path.lastIndexOf("/")+1); open(file, e.getURL().getRef()); } else { jumpTo(e.getURL().getRef()); } } } }); // Listener to do stuff when the page loaded textPane.addPropertyChangeListener("page", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { pageLoaded(); } }); // ActionListener for all the navigation buttons ActionListener buttonAction = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { HistoryItem item = null; if (e.getActionCommand().equals("historyBack")) { item = history.backward(); } else if (e.getActionCommand().equals("historyForward")) { item = history.forward(); } else if (e.getActionCommand().equals("home")) { open(null, null); } else if (e.getActionCommand().equals("up")) { open(currentPage, ""); } else if (e.getActionCommand().equals("web")) { String url = BASE_HELP_URL+currentLocation.getText(); UrlOpener.openUrlPrompt(About.this, url, true); } // Change to valid history item if (item != null) { open(item.page, item.ref); } } }; // Toolbar with navigation buttons and location info (text field) JToolBar toolbar = new JToolBar(); historyBackButton = makeButton("go-previous.png", "historyBack", "Go back"); historyBackButton.addActionListener(buttonAction); historyForwardButton = makeButton("go-next.png", "historyForward", "Go forward"); historyForwardButton.addActionListener(buttonAction); JButton homeButton = makeButton("go-home.png", "home", "Main Help Page"); homeButton.addActionListener(buttonAction); JButton upButton = makeButton("go-up.png", "up", "Scroll To Top"); upButton.addActionListener(buttonAction); JButton webButton = makeButton("go-web.png", "web", "Open in online help"); webButton.addActionListener(buttonAction); currentLocation = new JTextField(20); JPanel currentLocationPanel = new JPanel(new BorderLayout()); currentLocationPanel.setBorder(BorderFactory.createEmptyBorder(4, 5, 5, 5)); currentLocationPanel.setOpaque(false); currentLocationPanel.add(currentLocation); toolbar.setFloatable(false); toolbar.add(historyBackButton); toolbar.add(historyForwardButton); toolbar.add(homeButton); toolbar.add(upButton); toolbar.addSeparator(); toolbar.add(currentLocationPanel); toolbar.add(webButton); toolbar.setMargin(new Insets(5,3,2,3)); add(toolbar, BorderLayout.PAGE_START); updateHistoryButtons(); add(scroll); scroll.setPreferredSize(new Dimension(680,400)); open(null, null); // Close button JButton button = new JButton("Close"); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (Chatty.DEBUG) { reload(); } else { setVisible(false); } } }); add(button,BorderLayout.SOUTH); pack(); } private JButton makeButton(String image, String action, String tooltip) { JButton button = new JButton(); button.setActionCommand(action); button.setIcon(new ImageIcon(About.class.getResource(image))); button.setToolTipText(tooltip); return button; } /** * Loads the given page into the window. This only changes the page, not * the position in the page, although loading the page can trigger just * that. * * @param page */ private void loadPage(String page) { try { textPane.setPage(getClass().getResource(page)); currentPage = page; } catch (IOException ex) { LOGGER.warning("Invalid page: "+page+" ("+ex.getLocalizedMessage()+")"); } } /** * Close window (or reload page if in DEBUG mode) * * @param e */ @Override public void actionPerformed(ActionEvent e) { } /** * Open a page and go to a position (ref, which is an anchor in the HTML * document) on that page. * * @param page The filename of the page to load, loads the default page if * this is {@code null} * @param ref The position to jump to, sets to empty String if {@code null} */ public final void open(String page, String ref) { if (ref == null) { ref = ""; } ref = ref.replace(" ", "_"); if (page == null) { page = DEFAULT_PAGE; } if (currentPage != null && currentPage.equals(page)) { // If already on this page, just jump to the position jumpTo(ref); } else { // Else save position for later, so it can jump there after loading reference = ref; } loadPage(page); // Add opened location to history (doesn't change if it's the same as // it is already on this position in the history) HistoryItem location = new HistoryItem(page, ref); history.addIfNew(location); updateHistoryButtons(); currentLocation.setText(location.toString()); } /** * Enable/disable the history buttons based on whether there is something * to go to in the history. */ private void updateHistoryButtons() { historyForwardButton.setEnabled(history.hasNext()); historyBackButton.setEnabled(history.hasPrevious()); } /** * Jump to the given position (ref) in the current page * * @param ref The position to jump to, if it is an empty String, then it * will scroll to the very top * @throws NullPointerException If {@code ref} is {@code null} */ private void jumpTo(final String ref) { if (ref.isEmpty()) { textPane.scrollRectToVisible(new Rectangle(0,0,1,1)); } else { textPane.scrollToReference(ref); } currentReference = ref; } /** * Once the page is loaded, jump to the position, if one is saved for this * page. */ private void pageLoaded() { if (reference != null) { jumpTo(reference); reference = null; } } /** * Reloads the current page and jumps to the same position. This is only * used for testing purposes, because usually the pages shouldn't change * while the program is running. */ private void reload() { Document doc = textPane.getDocument(); doc.putProperty(Document.StreamDescriptionProperty, null); reference = currentReference; loadPage(currentPage); } /** * Saves a combination of page and ref which denotes a location in the help. * Used for the history. */ private static class HistoryItem { private final String page; private final String ref; HistoryItem(String page, String ref) { this.page = page; this.ref = ref; } @Override public boolean equals(Object obj) { if (obj != null && obj instanceof HistoryItem) { HistoryItem obj2 = (HistoryItem)obj; return page.equals(obj2.page) && ref.equals(obj2.ref); } return false; } @Override public String toString() { if (ref.isEmpty()) { return page; } return page+"#"+ref; } } }