/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program 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 2.1 of the License, or (at your option) any later version.
*
* This program 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 this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2005, by :
* Corporate:
* EADS Astrium SAS
* EADS CRC
* Individual:
* Claude Cazenave
*
* $Id: HelpViewer.java,v 1.31 2009/01/20 12:20:48 ogor Exp $
*
* Changes
* -------
* 5 juil. 2006 : Initial public release (CC);
*
*/
package jsynoptic.ui;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Stack;
import java.util.StringTokenizer;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLFrameHyperlinkEvent;
import javax.swing.text.html.StyleSheet;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import simtools.shapes.AbstractShape;
import simtools.ui.GridBagPanel;
import simtools.ui.MenuResourceBundle;
import simtools.ui.ResourceFinder;
import simtools.util.CurrentPathProvider;
import jsynoptic.base.HelpExtractor;
import jsynoptic.base.HelpNode;
import jsynoptic.base.Plugin;
import jsynoptic.builtin.Builtin;
/**
*
* @author zxpletran007
* @version 1.6 2006
*/
public class HelpViewer extends JDialog implements HyperlinkListener, TreeSelectionListener, ActionListener {
/** Resources */
public static MenuResourceBundle resources = ResourceFinder.getMenu(HelpViewer.class);
/** Stacks for forward, backward actions */
protected Stack backward, forward;
/**
* Help pages are displayed into this pane
*/
protected AntiAliasedTextPane viewer;
/**
* viewer style
*/
protected StyleSheet viewerStyle;
/**
* Help table of contents is displayed into this tree
*/
protected JTree helpTree;
/**
* Help root node. The root node in the help structure
*/
protected HelpNode rootNode;
/**
* Help tree root node. The root node in the help tree
*/
protected DefaultMutableTreeNode rootTreeNode;
/**
* tools bar buttons
*/
protected JButton bHome, bSave, bBack, bNext;
/**
* The help current page
*/
protected URL currentPage;
/**
* Search text field
*/
protected JTextField searchField;
/**
* Search results are displayed into this tree
*/
protected JTree searchTree;
protected JButton bClearFilter, bApplyFilter;
/**
* Search tree root node. The root node in the help tree
*/
protected DefaultMutableTreeNode searchRootTreeNode;
/**
* Search tree model
*/
protected DefaultTreeModel searchTreeModel;
/**
* The parser used to get HTML pages text content
*/
protected HTMLParser htmlParser;
protected JFileChooser exporthelpDirectoryChooser;
/**
* target --> node in helpTree table
*/
protected HashMap targetToNodeTable;
public HelpViewer(Frame parent, String title) {
super(parent, title, false);
backward = new Stack();
forward = new Stack();
// Create help page viewer
viewer = new AntiAliasedTextPane();
JScrollPane scrollPane = new JScrollPane(viewer);
viewer.setEditable(false);
viewer.addHyperlinkListener(this);
// Create tools bar
bHome = JSynopticPanels.resources.getBox("home", this);
bSave = JSynopticPanels.resources.getBox("save", this);
bBack= JSynopticPanels.resources.getBox("prev", this);
bNext = JSynopticPanels.resources.getBox("next", this);
JToolBar toolsBar = new JToolBar();
toolsBar.add(bHome);
toolsBar.add(bSave);
toolsBar.addSeparator();
toolsBar.add(bBack);
toolsBar.add(bNext);
bBack.setEnabled(!backward.isEmpty());
bNext.setEnabled(!forward.isEmpty());
targetToNodeTable = new HashMap();
// Create help tree
rootNode = new HelpNode();
rootTreeNode = new DefaultMutableTreeNode();
DefaultTreeModel treeModel = new DefaultTreeModel(rootTreeNode);
helpTree = new JTree();
helpTree.setModel(treeModel);
helpTree.setRootVisible(false);
helpTree.setShowsRootHandles(true);
helpTree.addTreeSelectionListener(this);
// Renderer
DefaultTreeCellRenderer helpTreeRenderer = (DefaultTreeCellRenderer) helpTree.getCellRenderer();
helpTreeRenderer.setOpenIcon(resources.getIcon("folderIcon"));
helpTreeRenderer.setClosedIcon(resources.getIcon("folderIcon"));
helpTreeRenderer.setLeafIcon(resources.getIcon("leafIcon"));
createHelpContents();
// Create help search panel
// search tree
searchRootTreeNode = new DefaultMutableTreeNode();
searchTreeModel = new DefaultTreeModel(searchRootTreeNode);
searchTree = new JTree();
searchTree.setModel(searchTreeModel);
searchTree.setRootVisible(false);
searchTree.setShowsRootHandles(true);
searchTree.addTreeSelectionListener(this);
JScrollPane scrollSearchTree = new JScrollPane(searchTree, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
DefaultTreeCellRenderer searchTreeRenderer = (DefaultTreeCellRenderer) searchTree.getCellRenderer();
searchTreeRenderer.setOpenIcon(resources.getIcon("folderIcon"));
searchTreeRenderer.setClosedIcon(resources.getIcon("folderIcon"));
searchTreeRenderer.setLeafIcon(resources.getIcon("leafIcon"));
// search field
JToolBar toolbar = new JToolBar();
toolbar.setRollover(true);
toolbar.setFloatable(false);
bClearFilter = resources.getBox("clearFilter", this);
bClearFilter.setEnabled(false);
toolbar.add(bClearFilter);
bApplyFilter = resources.getBox("applyFilter", this);
toolbar.add(bApplyFilter);
JLabel searchTip = new JLabel(resources.getString("filter"));
searchField = new JTextField(15);
searchField.addKeyListener(
new KeyListener() {
public void keyPressed(KeyEvent keyEvent) {}
public void keyReleased(KeyEvent keyEvent) {
boolean enable = !searchField.getText().equals("");
bApplyFilter.setEnabled(enable);
bClearFilter.setEnabled(enable);
}
public void keyTyped(KeyEvent keyEvent) {}
}
);
searchField.addActionListener(this);
// HTML parser
htmlParser = new SourceForgeHTMLParser();
GridBagPanel helpSearchPanel = new GridBagPanel();
helpSearchPanel.addOnCurrentRow(searchTip);
helpSearchPanel.addOnCurrentRow(searchField, 3, true, false, false);
helpSearchPanel.addOnCurrentRow(toolbar,1,false, false, true);
helpSearchPanel.addOnCurrentRow(scrollSearchTree, 5, true, true, true);
// HELP PANES
JTabbedPane helpTreePane = new JTabbedPane();
// Tree contents pane
JScrollPane treeScrollPane = new JScrollPane(helpTree, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
helpTreePane.addTab(resources.getString("contents"),resources.getIcon("folderIcon"),treeScrollPane, resources.getString("contentsTips"));
// Search pane
helpTreePane.addTab(resources.getString("search"),resources.getIcon("searchIcon"),helpSearchPanel, resources.getString("searchTips"));
treeModel.reload();
// Create global frame
JSplitPane helpSlitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,helpTreePane ,scrollPane);
helpSlitPane.setDividerLocation(300);
Container content = getContentPane();
content.setLayout(new BorderLayout());
content.add(toolsBar, BorderLayout.NORTH);
content.add(helpSlitPane, BorderLayout.CENTER);
// Display first page
if (rootNode.getChildrenCount() != 0){
try {
displayPage( ((HelpNode)rootNode.getChildren().get(0)).getLink() );
} catch (IOException e) {
e.printStackTrace();
}
}
// Set up Directory chooser
File currentPath = CurrentPathProvider.currentPathProvider.getCurrentPath();
exporthelpDirectoryChooser = new JFileChooser(currentPath);
exporthelpDirectoryChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
exporthelpDirectoryChooser.setDialogTitle(resources.getString("selectExportdirectory"));
}
private void createHelpContents(){
// Get help content from plug-ins
if (Run.plugins!=null){
for (int i = 0; i < Run.plugins.size(); ++i) {
Plugin p = (Plugin) Run.plugins.get(i);
if (p != null) {
HelpNode hn = p.getHelp();
rootNode.addHelpNode(hn);
}
}
}
// Create the AWT help tree
for(int i =0; i < rootNode.getChildrenCount();i++){
rootTreeNode.add(createHelpTreeNode(rootNode.getChildAt(i)));
}
}
private DefaultMutableTreeNode createHelpTreeNode(HelpNode hn){
DefaultMutableTreeNode ret = new DefaultMutableTreeNode((Object)hn);
// Update node table
targetToNodeTable.put(hn.getLink(), ret);
// Add its children
for(int i =0; i< hn.getChildrenCount();i++){
ret.add( createHelpTreeNode(hn.getChildAt(i)));
}
return ret;
}
public void actionPerformed(ActionEvent e){
if ((e.getSource() == bHome)) {
// Go to first page
if (rootNode.getChildrenCount() != 0){
displayPageEvent( ((HelpNode)rootNode.getChildren().get(0)).getLink() );
}
}else if ((e.getSource() == bBack)) {
back();
}else if ((e.getSource() == bNext)) {
next();
}else if ((e.getSource() == bSave)) {
save();
} else if ((e.getSource() == bApplyFilter)) {
applySearchFilter();
} else if ((e.getSource() == bClearFilter)) {
searchField.setText("");
searchRootTreeNode.removeAllChildren(); // clean search tree
searchTreeModel.reload();
bClearFilter.setEnabled(false);
bApplyFilter.setEnabled(false);
} else if ((e.getSource() == searchField)) {
applySearchFilter();
}
}
protected void applySearchFilter(){
searchRootTreeNode.removeAllChildren(); // clean search tree
String[] keywords =searchField.getText().split(" "); // TODO all kinds of separators ?
// Parse all html documents
for(int i=0;i<rootNode.getChildrenCount();i++){
searchKeywordsInNode(keywords, (HelpNode)rootNode.getChildAt(i) );
}
searchTreeModel.reload();
}
/**
* @param keywords
* @param node
*/
protected void searchKeywordsInNode(String[] keywords, HelpNode node){
// Do not consider inner page links
if (node!= null && node.getLink() != null && node.getLink().toString().indexOf("#")==-1){
String result = htmlParser.extractText(node.getLink().toString());
boolean containsAllKeyword= (keywords.length!=0);
for(int j=0;j<keywords.length;j++)
containsAllKeyword&= (result.indexOf(keywords[j])!=-1);
if (containsAllKeyword){
searchRootTreeNode.add( new DefaultMutableTreeNode(node) );
}
}
// Do the search on children pages
for(int i=0;i< node.getChildrenCount();i++){
searchKeywordsInNode(keywords, (HelpNode)node.getChildAt(i) );
}
}
/**
* Extract help content into a selected output directory
*/
protected void save(){
if(exporthelpDirectoryChooser!=null){
int result=exporthelpDirectoryChooser.showSaveDialog(this);
if (result == JFileChooser.APPROVE_OPTION){
new HelpExtractor(rootNode, exporthelpDirectoryChooser.getSelectedFile());
}
}
}
/* (non-Javadoc)
* @see javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event.TreeSelectionEvent)
*/
public void valueChanged(TreeSelectionEvent e){
DefaultMutableTreeNode f = (DefaultMutableTreeNode) e.getPath().getLastPathComponent();
HelpNode hn = (HelpNode)f.getUserObject();
if (hn!=null){
displayPageEvent(hn.getLink());
}
}
/* (non-Javadoc)
* @see javax.swing.event.HyperlinkListener#hyperlinkUpdate(javax.swing.event.HyperlinkEvent)
*/
public void hyperlinkUpdate (HyperlinkEvent event){
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED){
if (event instanceof HTMLFrameHyperlinkEvent) {
HTMLDocument doc = (HTMLDocument)viewer.getDocument ();
doc.processHTMLFrameHyperlinkEvent((HTMLFrameHyperlinkEvent)event);
} else
displayPageEvent(event.getURL());
}
}
/**
* Display HTML page event and update backward stack
* @param page
*/
protected void displayPageEvent(URL page){
if (page != null && !page.equals(currentPage)){
try{
// Remind this page in backward stack
backward.push(currentPage);
bBack.setEnabled(!backward.isEmpty());
// Display HTML event
displayPage(page);
} catch (IOException ex){
}
}
}
/**
* Display HTML page and scroll Help if a node matches with the HTML URL address
* @param page
* @throws IOException
*/
protected void displayPage(URL page) throws IOException{
if (page!=null && !page.equals(currentPage)){
// Display page
viewer.setPage(page);
viewerStyle = ((HTMLDocument)viewer.getDocument()).getStyleSheet();
viewerStyle.importStyleSheet(Builtin.builtinHelp.getURLValue("styleSheet", null));
// Set as current page
currentPage = page;
// Get page tree path
DefaultMutableTreeNode node = (DefaultMutableTreeNode)targetToNodeTable.get(page);
if (node != null){
TreePath path = new TreePath(node.getPath());
// If page path has changed, update help tree and back stack
if (!path.equals(helpTree.getSelectionPath())){
helpTree.scrollPathToVisible(path);
helpTree.setSelectionPath(path);
}
}
}
}
protected void back(){
if (!backward.isEmpty()){
try {
forward.push(currentPage);
bBack.setEnabled(!backward.isEmpty());
bNext.setEnabled(!forward.isEmpty());
URL urlBack = (URL)backward.pop();
displayPage(urlBack);
} catch (IOException e) {
e.printStackTrace();
}
}
}
protected void next(){
if (!forward.isEmpty()){
try {
backward.push(currentPage);
bBack.setEnabled(!backward.isEmpty());
bNext.setEnabled(!forward.isEmpty());
URL urlNext = (URL)forward.pop();
displayPage(urlNext);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Display JSynoptic help dialog with all loaded plugin help pages.
* @param argv
*/
public static void main(String argv[]) {
Run.userProperties.read();
String defaultPlugins = Run.getProperties().getString("jsynoptic.plugins", "");
StringTokenizer st = new StringTokenizer(defaultPlugins, ", ;:\t|");
while (st.hasMoreTokens()) {
Run.loadPlugin(st.nextToken());
}
HelpViewer e = new HelpViewer(new JFrame(), "JSynoptic help");
e.setSize(1000, 600);
e.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent ev) {
System.exit(0);
}
});
e.show();
}
public class AntiAliasedTextPane extends JTextPane {
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
AbstractShape.ANTI_ALIASING? RenderingHints.VALUE_TEXT_ANTIALIAS_ON: RenderingHints.VALUE_TEXT_ANTIALIAS_OFF
);
g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
super.paintComponent(g2);
}
}
}