/* ****************************************************************************** * * Copyright 2008-2010 Hans Dijkema * * JRichTextEditor is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * JRichTextEditor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with JRichTextEditor. If not, see <http://www.gnu.org/licenses/>. * * ******************************************************************************/ package nl.dykema.jxmlnote.help; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Desktop; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.net.URI; import java.net.URL; import java.util.Enumeration; import java.util.Hashtable; import java.util.Stack; import java.util.WeakHashMap; import java.util.concurrent.Semaphore; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import nl.dykema.jxmlnote.JXMLNoteViewer; import nl.dykema.jxmlnote.document.XMLNoteDocument; import nl.dykema.jxmlnote.document.XMLNoteImageIcon; import nl.dykema.jxmlnote.document.XMLNoteImageIconSize; import nl.dykema.jxmlnote.document.XMLNoteMark; import nl.dykema.jxmlnote.document.XMLNoteImageIcon.LoadedListener; import nl.dykema.jxmlnote.exceptions.BadStyleException; import nl.dykema.jxmlnote.exceptions.DefaultXMLNoteErrorHandler; import nl.dykema.jxmlnote.help.data.HelpEntry; import nl.dykema.jxmlnote.help.data.HelpImage; import nl.dykema.jxmlnote.help.data.HelpTopic; import nl.dykema.jxmlnote.help.data.HelpImage.ImageLoadedListener; import nl.dykema.jxmlnote.interfaces.MarkMarkupProvider; import nl.dykema.jxmlnote.interfaces.MarkMarkupProviderMaker; import nl.dykema.jxmlnote.interfaces.XMLNotePreferences; import nl.dykema.jxmlnote.internationalization.DefaultXMLNoteTranslator; import nl.dykema.jxmlnote.internationalization.XMLNoteTranslator; import nl.dykema.jxmlnote.report.ReportException; import nl.dykema.jxmlnote.report.ReportProgressBar; import nl.dykema.jxmlnote.report.XMLNoteToReport; import nl.dykema.jxmlnote.report.ReportProgressBar.Progress; import nl.dykema.jxmlnote.report.pdf.PdfReport; import nl.dykema.jxmlnote.report.viewers.PdfViewer; import nl.dykema.jxmlnote.toolbar.JXMLNoteIcon; import nl.dykema.jxmlnote.toolbar.JXMLNoteToolBar; import nl.dykema.jxmlnote.widgets.JXMLNotePane; import nl.dykema.jxmlnote.widgets.marks.DefaultMarkMarkupProvider; import nl.dykema.jxmlnote.widgets.marks.MarkMouseListener; public class JHelpViewer extends JPanel implements TreeSelectionListener, XMLNoteImageIcon.Provider, MarkMouseListener, ActionListener { private static final long serialVersionUID = 1L; private DefaultTreeModel _treeModel; private JTree _tree; private HelpTopic _current; private XMLNoteDocument _document; private JXMLNoteViewer _viewer; private JXMLNoteToolBar _toolbar; private JarFile _helpjar; private JSplitPane _splitpane; private XMLNotePreferences _prefs; private DefaultMutableTreeNode _topics; private Stack<HelpTopic> _pastTopics; private Stack<HelpTopic> _futureTopics; private XMLNoteTranslator _tr; private DefaultMarkMarkupProvider _markProvider; private DefaultMarkMarkupProvider _hyperlinkProvider; private Color _cTopic=Color.blue; private Color _cHyperlink=new Color(101,0,168); private boolean _inSession=false; private boolean _canceled=false; private String getPrefKey(String k) { String name = JHelpViewer.this.getName(); if (name == null) { name = "default"; } String nm = this.getClass().getName() + "." + name + "." + k; nm.replaceAll("[.]", "_"); return nm; } private void createGUI() throws BadStyleException { BorderLayout layout = new BorderLayout(); layout.setHgap(3); layout.setVgap(3); super.setLayout(layout); _tree = new JTree(_treeModel); _tree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION); _tree.setRootVisible(false); _tree.addTreeSelectionListener(this); _document = new XMLNoteDocument(); _document.setImageSupport(true); _document.setXMLNoteImageIconProvider(this); _markProvider = new DefaultMarkMarkupProvider( MarkMarkupProvider.MarkupType.UNDERLINED, _cTopic); _markProvider.setTextColor(_cTopic); _hyperlinkProvider = new DefaultMarkMarkupProvider( MarkMarkupProvider.MarkupType.UNDERLINED, _cHyperlink); _hyperlinkProvider.setTextColor(_cHyperlink); _viewer = new JXMLNoteViewer(_document, new MarkMarkupProviderMaker() { public MarkMarkupProvider create(String markId, String markClass) { if (markClass.startsWith("TopicLink:")) { return _markProvider; } else { return _hyperlinkProvider; } } }); _viewer.pane().addMarkMouseListener(this); _viewer.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); _splitpane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(_tree), _viewer); _splitpane.setContinuousLayout(true); _splitpane.addPropertyChangeListener( JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { Object o = e.getNewValue(); if (o instanceof Integer) { _prefs.put(getPrefKey("Dividerlocation"), (Integer) o); } } }); _splitpane.setDividerLocation(_prefs.getInt(getPrefKey("Dividerlocation"), 150)); _toolbar = _viewer.toolbar(); _toolbar.setButtonSize(24); _toolbar.removeAllSections(); _toolbar.addSection("navigate"); _toolbar.add("navigate", "previous", _tr._("Previous topic"), this); _toolbar.add("navigate", "next", _tr._("Future topic"), this); _toolbar.addSection("printing"); _toolbar.add("printing","print",_tr._("Print topic"),this); _toolbar.addSection("@zoom"); double z=Double.parseDouble(_prefs.getString(getPrefKey("Zoom"), "1.0")); if (z<0.25) { z=0.25; } _viewer.pane().setZoomFactor(z); _viewer.pane().addPropertyChangeListener(JXMLNotePane.PROPERTY_ZOOM, new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { Object o=e.getNewValue(); if (o instanceof Double) { _prefs.put(getPrefKey("Zoom"),Double.toString((Double) o)); } } }); JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); super.add(_splitpane, BorderLayout.CENTER); } public void createPdf(File output) { final PdfReport pdf; final XMLNoteToReport report; try { pdf = new PdfReport(null); // Help is part of an application, so expect the font mapper // to initialize fonts elsewhere final File reportFile=output; pdf.beginReport(reportFile); report=new XMLNoteToReport(pdf); _canceled=false; ReportProgressBar bar=ReportProgressBar.runJob(pdf, this, _tr._("Printing"), new ReportProgressBar.Job() { public void job(Progress p) { try { p.progress(0); p.statusMessage(String.format(_tr._("Creating %s ..."), reportFile.getName())); pdf.addXMLNote(_document,new MarkMarkupProviderMaker() { public MarkMarkupProvider create(String markId,String markClass) { return _markProvider; } }, null); p.progress(100); pdf.endReport(); } catch (ReportException e) { DefaultXMLNoteErrorHandler.exception(e); } } public void cancelled() { try { pdf.cancel(); _canceled=true; } catch (ReportException e) { DefaultXMLNoteErrorHandler.exception(e); } } }, _prefs); } catch (ReportException e) { DefaultXMLNoteErrorHandler.exception(e); _canceled=true; } } boolean validCoord(int x,int y,int w,int h) { Toolkit kit=Toolkit.getDefaultToolkit(); Dimension s=kit.getScreenSize(); if (x<0 || y<0) { return false; } if (((x+w)>s.width) || ((y+h)>s.height)) { return false; } return true; } private void doPrint() { try { File output=File.createTempFile("help", "pdf"); createPdf(output); if (_canceled) { return; } Window parent=SwingUtilities.getWindowAncestor(this); int x=_prefs.getInt("pdfView_x", -1); int y=_prefs.getInt("pdfView_y", -1); int w=_prefs.getInt("pdfView_w", -1); int h=_prefs.getInt("pdfView_h", -1); PdfViewer viewer=PdfViewer.createPdfViewer(parent,this,_tr._("Print preview"),output,_prefs); if (validCoord(x,y,w,h)) { viewer.setLocation(x, y); viewer.setPreferredSize(new Dimension(w,h)); } viewer.setVisible(true); x=viewer.getLocation().x; y=viewer.getLocation().y; w=viewer.getSize().width; h=viewer.getSize().height; if (validCoord(x,y,w,h)) { _prefs.put("pdfView_x", x); _prefs.put("pdfView_y", y); _prefs.put("pdfView_w", w); _prefs.put("pdfView_h", h); } viewer.closePdf(); viewer.dispose(); output.delete(); } catch (IOException e) { DefaultXMLNoteErrorHandler.exception(e); } } public JXMLNoteToolBar toolbar() { return _toolbar; } private void createData() { _topics = new DefaultMutableTreeNode(new HelpTopic("Help Topics", "")); _treeModel = new DefaultTreeModel(_topics); loadHelp(); } public void valueChanged(TreeSelectionEvent e) { TreePath path = e.getPath(); DefaultMutableTreeNode topic = (DefaultMutableTreeNode) path.getLastPathComponent(); HelpTopic previous=_current; HelpTopic ht = (HelpTopic) topic.getUserObject(); loadTopic(ht); if (!_inSession && previous!=null) { _futureTopics.clear(); _pastTopics.push(previous); } } private XMLNoteImageIcon _nullImage = null; private XMLNoteImageIcon nullImage() { if (_nullImage == null) { _nullImage = new XMLNoteImageIcon(new JXMLNoteIcon("NoImage", 50, 50)); _nullImage.setId("__null__"); } return _nullImage; } public XMLNoteImageIcon getIcon(String id) { XMLNoteImageIcon img = loadImage(id); if (img == null) { return nullImage(); } else { return img; } } // This cannot occur in help files public XMLNoteImageIcon getIcon(URL url, String description,XMLNoteImageIconSize sz) { return nullImage(); } WeakHashMap<String,XMLNoteImageIcon> cache=new WeakHashMap<String,XMLNoteImageIcon>(); private XMLNoteImageIcon loadImage(String id) { XMLNoteImageIcon cicn=cache.get(id); if (cicn!=null) { return cicn; } else { ZipEntry ize = _helpjar.getEntry(id + ".img"); try { final InputStream in = _helpjar.getInputStream(ize); final HelpImage img = new HelpImage(); // (HelpImage) dec.readObject(); final Semaphore sem=new Semaphore(0); final Dimension imageDim=new Dimension(); XMLNoteImageIconSize sz=img.read(in,imageDim,new ImageLoadedListener() { public void loaded() { try { in.close(); } catch (Exception E) { DefaultXMLNoteErrorHandler.exception(E); } sem.release(); } }); final XMLNoteImageIcon icn = new XMLNoteImageIcon(id,sz,imageDim,""); Runnable R=new Runnable() { public void run() { try { sem.acquire(); } catch (InterruptedException e) { // unexpected, // however, do nothing } icn.initialize( img.getImageIcon(), new LoadedListener() { public void loaded() { icn.setDescription(img.getDescription()); SwingUtilities.invokeLater(new Runnable() { public void run() { //System.out.println("firechanged"); //_document.fireChanged(); JHelpViewer.this.repaint(); } }); } } ); } }; Thread thr=new Thread(R); thr.start(); cache.put(id, icn); return icn; } catch (Exception E) { DefaultXMLNoteErrorHandler.exception(E); return null; } } } /** * External interface to the help viewer, to programmatically set the help * topic. * * @param topicKey */ public void setTopic(String topicKey) { Enumeration<DefaultMutableTreeNode> en = _topics.breadthFirstEnumeration(); while (en.hasMoreElements()) { DefaultMutableTreeNode nd = en.nextElement(); HelpTopic tp = (HelpTopic) nd.getUserObject(); if (tp.getKey().equals(topicKey)) { if (_current != null) { _futureTopics.clear(); _pastTopics.push(_current); } _inSession=true; TreePath path = new TreePath(nd.getPath()); _tree.setSelectionPath(path); _inSession=false; return; } } } private void setTopicToTopicId(String topicId, boolean notFromNavigateButtons) { Enumeration<DefaultMutableTreeNode> en = _topics.breadthFirstEnumeration(); while (en.hasMoreElements()) { DefaultMutableTreeNode nd = en.nextElement(); HelpTopic tp = (HelpTopic) nd.getUserObject(); if (tp.getTopicId().equals(topicId)) { if (_current != null) { if (notFromNavigateButtons) { _futureTopics.clear(); _pastTopics.push(_current); } } _inSession=true; TreePath path = new TreePath(nd.getPath()); _tree.setSelectionPath(path); _inSession=false; return; } } } public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if (cmd.equals("previous")) { if (!_pastTopics.isEmpty()) { HelpTopic c=_current; HelpTopic tp = _pastTopics.pop(); if (c!=null) { _futureTopics.push(c); } setTopicToTopicId(tp.getTopicId(), false); } } else if (cmd.equals("next")) { if (!_futureTopics.isEmpty()) { HelpTopic c=_current; HelpTopic tp = _futureTopics.pop(); if (c!=null) { _pastTopics.push(c); } setTopicToTopicId(tp.getTopicId(), false); } } else if (cmd.equals("print")) { doPrint(); } } public void markClicked(XMLNoteMark m, MouseEvent e) { e.consume(); String cl=m.markClass(); if (cl.startsWith("TopicLink:")) { String topicId = cl.replaceFirst("TopicLink[:]", ""); setTopicToTopicId(topicId, true); } else if (cl.startsWith("HyperLink:")) { String link = cl.replaceFirst("HyperLink[:]", ""); try { URI uri=new URI(link); Desktop.getDesktop().browse(uri); } catch (Exception E) { JOptionPane.showMessageDialog(this,_tr._("Cannot show the requested hyperlink"), _tr._("Hyperlink (WWW) choosen"),JOptionPane.WARNING_MESSAGE); } } } public void markDoubleClicked(XMLNoteMark m, MouseEvent e) { e.consume(); } public Cursor mouseMovedIntoMark(XMLNoteMark m, MouseEvent e) { return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } public void mouseMovedOutOfMark(XMLNoteMark m, MouseEvent e) { } private void loadTopic(HelpTopic ht) { _current = ht; JarFile hj = _helpjar; ZipEntry hze = hj.getEntry(ht.getTopicId() + ".top"); try { InputStream in = hj.getInputStream(hze); ObjectInputStream dec = new ObjectInputStream(in); HelpEntry he; if (JHelpEditor.NEW_READ) { he = new HelpEntry(); he.readObject(dec); } else { he = (HelpEntry) dec.readObject(); } in.close(); _document.resetFromXML(he.getXmlnote()); } catch (Exception E) { DefaultXMLNoteErrorHandler.exception(E); } } private void loadHelp() { _topics = new DefaultMutableTreeNode(new HelpTopic("Help Topics", "")); _pastTopics.clear(); _futureTopics.clear(); try { JarFile hj = _helpjar; ZipEntry ze = hj.getEntry("HelpTopics.xnhlp"); if (ze != null) { InputStream in = hj.getInputStream(ze); ObjectInputStream dec = new ObjectInputStream(in); if (JHelpEditor.NEW_READ) { Hashtable<String, HelpTopic> htt = new Hashtable<String, HelpTopic>(); DefaultMutableTreeNode ids = (DefaultMutableTreeNode) dec.readObject(); Enumeration<DefaultMutableTreeNode> en = (Enumeration<DefaultMutableTreeNode>) ids.breadthFirstEnumeration(); while (en.hasMoreElements()) { DefaultMutableTreeNode id = en.nextElement(); HelpTopic ht = new HelpTopic(); ht.readObject(dec); htt.put(ht.getTopicId(), ht); } en = (Enumeration<DefaultMutableTreeNode>) ids.breadthFirstEnumeration(); while (en.hasMoreElements()) { DefaultMutableTreeNode id = en.nextElement(); String t_id = (String) id.getUserObject(); id.setUserObject(htt.get(t_id)); } _topics = ids; } else { _topics = (DefaultMutableTreeNode) dec.readObject(); } in.close(); } } catch (Exception e) { DefaultXMLNoteErrorHandler.exception(e); } _treeModel.setRoot(_topics); if (_tree != null && _tree.getRowCount() > 1) { _tree.setSelectionRow(1); } } public void loadOther(JarFile helpfile) { _helpjar = helpfile; loadHelp(); } public JHelpViewer(JarFile helpfile, XMLNotePreferences prefs) throws BadStyleException, IOException { _helpjar = helpfile; _prefs = prefs; _pastTopics = new Stack<HelpTopic>(); _futureTopics = new Stack<HelpTopic>(); _tr = new DefaultXMLNoteTranslator(); _current = null; createData(); createGUI(); if (_tree.getRowCount() > 0) { _tree.setSelectionRow(0); } } }