/*

Copyright 2006 Eric Hakenholz

This file is part of C.a.R. software.

    C.a.R. is a free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, version 3 of the License.

    C.a.R. 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

 */

package eric;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.metal.MetalComboBoxUI;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;

import rene.gui.Global;

/**
 * Info class. Reads a file "info.txt" or "de_info.txt" etc. that has the
 * structure
 *
 * .subject1 substitute1 substitute2 ... .related subject subject ... Header ...
 *
 * .subject2 ...
 *
 * and displays the text, starting from header, searching for a subject or any
 * of its substitutes. The headers of the related subjects are presented in a
 * choice list. The user can switch to any of it.
 *
 * There is a history and a back button.
 *
 * Moroever, there is a search button, that displays the first subject
 * containing a string and presents all other subjects containing the string in
 * the choice list.
 */
public class JHelpPanel extends JEricPanel {

    private static final long serialVersionUID = 1L;
    MyJTextPane T;
    public static String Subject = "start";
    String Search = null;
    // MyChoice2 L;
    Vector Other = null;
    Vector History = new Vector();
    Vector Related;
    JEricPanel north;
    MyJTextSearch TSearch;

    public JHelpPanel() {
	T = new MyJTextPane(this);
	final JScrollPane scrollPane = new JScrollPane(T);
	scrollPane.setBorder(javax.swing.BorderFactory.createEmptyBorder());
        setAlignmentX(0.0F);
	setAlignmentY(0.0F);

	setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS));
	setFocusable(false);

	north = new JEricPanel();
	north.setLayout(new javax.swing.BoxLayout(north, javax.swing.BoxLayout.Y_AXIS));
	north.setOpaque(false);
	TSearch = new MyJTextSearch(this);
	Subject = "start";
	fill(true);
	north.add(TSearch);
	north.add(vmargin(2));

	add(north);
	add(scrollPane);
    }

    public void focusTxt() {
	SwingUtilities.invokeLater(new Runnable() {

	    @Override
	    public void run() {
		TSearch.JTF.requestFocus();
	    }
	});
    }

    static JEricPanel margin(final int w) {
	final JEricPanel mypan = new JEricPanel();
	fixsize(mypan, w, 1);
	mypan.setLayout(new javax.swing.BoxLayout(mypan, javax.swing.BoxLayout.X_AXIS));
	mypan.setAlignmentX(0F);
	mypan.setAlignmentY(0F);
	mypan.setOpaque(false);
	mypan.setFocusable(false);
	return mypan;
    }

    static JEricPanel vmargin(final int h) {
	final JEricPanel mypan = new JEricPanel();
	fixsize(mypan, 1, h);
	mypan.setLayout(new javax.swing.BoxLayout(mypan, javax.swing.BoxLayout.X_AXIS));
	mypan.setAlignmentX(0F);
	mypan.setAlignmentY(0F);
	mypan.setOpaque(false);
	mypan.setFocusable(false);
	return mypan;
    }

    // Only called by LeftPanelContent init method :
    public void fixPanelSize(final int w, final int h) {
	fixsize(north, w, 24);
	north.revalidate();
	fixsize(TSearch, w, 24);
	fixsize(TSearch.wholepanel, w - 20, 18);
    }

    public static void fixsize(final JComponent cp, final int w, final int h) {
	final Dimension d = new Dimension(w, h);
	cp.setMaximumSize(d);
	cp.setMinimumSize(d);
	cp.setPreferredSize(d);
	cp.setSize(d);
    }

    public void clearSearchTxtField() {
	TSearch.JTF.setText("");
    }

    public final void fill(final boolean WithTextFocus) {
	final boolean vis = TSearch.ispopupvisible();
	if (vis) {
	    TSearch.hidepopup();
	}
	TSearch.L = new MyJPopupMenu(this);
	T.setText("");

	String TXT = "";

	boolean Found = false, Appending = false;
	Related = null;
	Other = new Vector();
	String pair[] = null, lastpair[] = null;
	String lang = Global.name("language", "");
	Vector SearchResults = new Vector();
	String SearchResult = "";
	String FoundTopic = null;
	boolean FirstRun = true, FoundHeader = false;

	String Search1 = Search;
	if (Search != null && Search.length() > 0) {
	    Search1 = Search.substring(0, 1).toUpperCase() + Search.substring(1);
	}

	read: while (true) {
	    try {
		Global.name("codepage.help", "");
		BufferedReader in = null;
		try {
		    in = new BufferedReader(new InputStreamReader(
		    new FileInputStream(Global.getHomeDirectory() + "docs/" + lang + "info.txt"), "UTF8"));
		} catch (final Exception ex) {
		    in = new BufferedReader(new InputStreamReader(
		    new FileInputStream(Global.getHomeDirectory() + "docs/" + lang + "info.txt")));
		}

		boolean newline = false;
		newline: while (true) {
		    String s = in.readLine();
		    if (s == null) {
			break newline;
		    }
		    if (s.startsWith("//")) {
			continue;
		    }
		    s = clear(s);
		    if (!s.startsWith(".") && Search != null && (s.indexOf(Search) >= 0 || s.indexOf(Search1) >= 0)) {
			if (lastpair != null && pair == null && !SearchResult.equals(lastpair[0])) {
			    SearchResults.addElement(lastpair);

			    SearchResult = lastpair[0];
			    if (FoundTopic == null) {
				FoundTopic = lastpair[0];
			    }
			}
		    }
		    interpret: while (true) {
			if (!Appending && s.startsWith(".") && !s.startsWith(".related")) {
			    if (!Found) {
				if (s.startsWith("." + Subject)) {
				    Found = true;
				    Appending = true;
				    continue newline;
				}
				final StringTokenizer t = new StringTokenizer(s);
				while (t.hasMoreElements()) {
				    final String name = t.nextToken();
				    if (name.equals(Subject)) {
					Found = true;
					Appending = true;
					continue newline;
				    }
				}
			    }
			    pair = new String[2];
			    s = s.substring(1);
			    final int n = s.indexOf(' ');
			    if (n > 0) {
				s = s.substring(0, n);
			    }
			    pair[0] = s;
			    continue newline;
			}
			if (Appending) {
			    if (s.startsWith(".related")) {
				s = s.substring(".related".length());
				Related = new Vector();
				final StringTokenizer t = new StringTokenizer(s);
				while (t.hasMoreElements()) {
				    Related.addElement(t.nextToken());
				}
				continue newline;
			    }
			    if (s.startsWith(".")) {
				Appending = false;
				continue interpret;
			    }
			    if (s.trim().equals("")) {
				if (!newline) {
				    // T.append("<br><br>");
				    TXT += "<br><br>";
				}
				newline = true;
			    } else {
				newline = false;
				if (s.startsWith(" ")) {
				    TXT += "<br>";
				    // T.append("<br>");
				}
				// T.append(s+" ");
				TXT += s + " ";
			    }
			} else if (pair != null && !s.startsWith(".")) {
			    pair[1] = s;
			    Other.addElement(pair);
			    lastpair = pair;
			    pair = null;
			    if (Search != null && (s.indexOf(Search) >= 0 || s.indexOf(Search1) >= 0)) {
				if (!SearchResult.equals(lastpair[0])) {
				    SearchResults.addElement(lastpair);
				    SearchResult = lastpair[0];
				    if (!FoundHeader) {
					FoundTopic = lastpair[0];
				    }
				    FoundHeader = true;
				}
			    }
			}
			continue newline;
		    }
		}
		// T.append("<br>");
		TXT += "<br>";
		in.close();
	    } catch (final Exception e) {
		if (!lang.equals("")) {
		    lang = "";
		    continue read;
		} else {
		    // V.appendLine(
		    // Global.name("help.error","Could not find the help file!"));
		    // T.append(Global.name("help.error",
		    // "Could not find the help file!"));
		    TXT += Global.name("help.error", "Could not find the help file!");
		}
	    }
	    if (FoundTopic != null && FirstRun) {
		Subject = FoundTopic;
		SearchResults = new Vector();
		SearchResult = "";
		pair = null;
		lastpair = null;
		Found = false;
		TXT = "";
		// T.setText("");
		// V.setText("");
		FirstRun = false;
		continue read;
	    } else {
		break read;
	    }
	}

	// if (!Found)
	// V.appendLine(Global.name("info.notfound","Topic not found!"));
	if (!Found) {
	    // T.append(Global.name("info.notfound", "Topic not found!"));
	    TXT += Global.name("info.notfound", "Topic not found!");
	}
	if (Search != null) {
	    if (SearchResults.size() > 0) {
		TSearch.L.addI(Global.name("info.searchresults"));
	    } else {
		TSearch.L.addI(Global.name("info.noresults"));
	    }
	} else {
	    TSearch.L.addI(Global.name("info.select"));
	}
	if (Search == null && Related != null) {
	    final Enumeration e = Related.elements();
	    while (e.hasMoreElements()) {
		final String topic = (String) e.nextElement();
		final Enumeration ev = Other.elements();
		while (ev.hasMoreElements()) {
		    final String s[] = (String[]) ev.nextElement();
		    if (s[0].equals(topic)) {
			TSearch.L.addI(s[1]);
			break;
		    }
		}
	    }
	}

	if (Search != null) {
	    final Enumeration e = SearchResults.elements();
	    while (e.hasMoreElements()) {
		final String s[] = (String[]) e.nextElement();
		TSearch.L.addI(s[1]);
	    }
	}
	T.setContent(TXT);
	if (WithTextFocus) {
	    focusTxt();
	}
	T.setCaretPosition(0);
	History.addElement(Subject);
	TSearch.setCount();
	if (vis) {
	    TSearch.showpopup();
	}
    }

    public String clear(String s) {
	s = s.replace('ß', ' ');
	s = s.replaceAll("__", "");
	return s;
    }
}

class MyComboBoxUI extends MetalComboBoxUI {

    public static ComponentUI createUI(final JComponent c) {
	return new MyComboBoxUI();
    }
}

class MyJTextSearch extends JEricPanel {

    private static final long serialVersionUID = 1L;
    JLabel cntLBL = new JLabel();
    JTextField JTF;
    JHelpPanel JP;
    JButton popBTN, clearBTN;
    JEricPanel txtpanel;
    JEricPanel wholepanel;
    MyJPopupMenu L;
    String old = "";

    @Override
    public void paintComponent(final java.awt.Graphics g) {
	super.paintComponent(g);
	final ImageIcon OffImage = new ImageIcon(getClass().getResource("/eric/GUI/icons/themes/common/helpsearchback.png"));
	final java.awt.Dimension d = this.getSize();
	g.drawImage(OffImage.getImage(), 0, 0, d.width, d.height, this);
    }

    public MyJTextSearch(final JHelpPanel jp) {
	super();
	setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.Y_AXIS));
	setOpaque(false);
	JP = jp;

	L = new MyJPopupMenu(JP);

	txtpanel = new JEricPanel();
	txtpanel.setLayout(new javax.swing.BoxLayout(txtpanel, javax.swing.BoxLayout.X_AXIS));
	txtpanel.setBorder(BorderFactory.createLineBorder(new Color(150, 150, 150), 1));
	txtpanel.setOpaque(true);
	txtpanel.setBackground(Color.WHITE);
	wholepanel = new JEricPanel();
	wholepanel.setLayout(new javax.swing.BoxLayout(wholepanel, javax.swing.BoxLayout.X_AXIS));
	wholepanel.setBorder(BorderFactory.createEmptyBorder());
	wholepanel.setOpaque(false);
	cntLBL.setOpaque(false);
	cntLBL.setText("");
	cntLBL.setFont(new Font("System", 0, 9));
	cntLBL.setHorizontalAlignment(SwingConstants.CENTER);
	cntLBL.setVerticalAlignment(SwingConstants.CENTER);
	JHelpPanel.fixsize(cntLBL, 20, 18);

	JTF = new JTextField();
	JTF.setFont(new Font("System", 0, 11));
	JTF.setForeground(new Color(50, 50, 50));
	JTF.setBackground(new Color(255, 255, 255));
	JTF.setBorder(BorderFactory.createEmptyBorder());
	JTF.setMargin(new java.awt.Insets(0, 0, 1, 0));
	JTF.addKeyListener(new KeyAdapter() {

	    @Override
	    public void keyReleased(final KeyEvent e) {
		if (JTF.getText().equals("")) {
		    clearBTN.setVisible(false);
		} else {
		    clearBTN.setVisible(true);
		}
		if ((e.getKeyCode() == KeyEvent.VK_ENTER) || (e.getKeyCode() == KeyEvent.VK_UP)) {
		    hidepopup();
		} else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
		    showpopup();
		} else {
		    doSearch(JTF.getText());
		}
	    }
	});
	// JTF.setFocusable(true);
	JTF.addFocusListener(new FocusAdapter() {

	    @Override
	    public void focusGained(final FocusEvent e) {
		JTF.selectAll();
	    }
	});

	final ImageIcon carimg = new ImageIcon(getClass().getResource("/eric/GUI/icons/themes/common/helpsearch1.gif"));
	popBTN = new JButton(carimg);
	popBTN.setBorder(BorderFactory.createEmptyBorder());
	popBTN.setOpaque(true);
	popBTN.setContentAreaFilled(true);
	popBTN.setFocusable(false);
	popBTN.addMouseListener(new MouseAdapter() {

	    @Override
	    public void mousePressed(final MouseEvent e) {
		showpopup();
	    }
	});

	final ImageIcon carimg2 = new ImageIcon(getClass().getResource("/eric/GUI/icons/themes/common/helpsearch2.png"));
	final JButton backBTN = new JButton(carimg2);
	backBTN.setBorder(BorderFactory.createEmptyBorder());
	backBTN.setOpaque(false);
	backBTN.setContentAreaFilled(false);
	backBTN.setFocusable(false);
	backBTN.addMouseListener(new MouseAdapter() {

	    @Override
	    public void mousePressed(final MouseEvent e) {
		final int n = JP.History.size();
		if (n < 2) {
		    return;
		}
		JP.History.removeElementAt(n - 1);
		JHelpPanel.Subject = (String) JP.History.elementAt(n - 2);
		JP.History.removeElementAt(n - 2);
		JP.fill(true);
	    }
	});

	final ImageIcon carimg3 = new ImageIcon(getClass().getResource("/eric/GUI/icons/themes/common/helpsearch3.gif"));
	clearBTN = new JButton(carimg3);
	clearBTN.setBorder(BorderFactory.createEmptyBorder());
	clearBTN.setOpaque(true);
	clearBTN.setContentAreaFilled(true);
	clearBTN.setFocusable(false);
	clearBTN.addMouseListener(new MouseAdapter() {
	    @Override
	    public void mousePressed(final MouseEvent e) {
		JTF.setText("");
		clearBTN.setVisible(false);
		doSearch("");
	    }
	});
	clearBTN.setVisible(false);
	JHelpPanel.fixsize(popBTN, carimg.getIconWidth(), carimg.getIconHeight());
	JHelpPanel.fixsize(clearBTN, carimg3.getIconWidth(), carimg3.getIconHeight());
	txtpanel.add(cntLBL);
	txtpanel.add(popBTN);
	txtpanel.add(JHelpPanel.margin(3));
	txtpanel.add(JTF);
	txtpanel.add(clearBTN);
	wholepanel.add(JHelpPanel.margin(5));
	wholepanel.add(txtpanel);
	wholepanel.add(JHelpPanel.margin(3));
	wholepanel.add(backBTN);
	this.add(JHelpPanel.vmargin(3));
	this.add(wholepanel);
    }

    void showpopup() {
	if (L.Count > 1) {
	    L.show(popBTN, 10, 20);
	}
    }

    void hidepopup() {
	L.setVisible(false);
    }

    boolean ispopupvisible() {
	return L.isVisible();
    }

    void doSearch(final String s) {
	if (s.equals("")) {
	    JP.Search = null;
	} else {
	    JP.Search = s;
	}
	JHelpPanel.Subject = "start";
	JP.fill(true);
	JP.Search = null;
    }

    void setCount() {
	if (L.Count > 1) {
	    cntLBL.setText("(" + (L.Count - 1) + ")");
	} else {
	    cntLBL.setText("");
	}
    }
}

class MyJPopupMenuItem extends JMenuItem {

    private static final long serialVersionUID = 1L;
    String STR;
    JHelpPanel JP;

    public MyJPopupMenuItem(final JHelpPanel jp, final String s) {
	super(s.replaceAll("<p[^/]*/p>", "").replace("<br >", ", "));
	STR = s;
	JP = jp;

	setFont(new Font("System", 0, 11));
	addMouseListener(new MouseAdapter() {

	    @Override
	    public void mousePressed(final MouseEvent arg0) {
		JP.TSearch.L.setVisible(false);
		JP.TSearch.JTF.setText("");
		JP.TSearch.clearBTN.setVisible(false);
		final String s = STR;
		final Enumeration ev = JP.Other.elements();
		while (ev.hasMoreElements()) {
		    final String p[] = (String[]) ev.nextElement();
		    if (p[1].equals(s)) {
			JHelpPanel.Subject = p[0];
			JP.fill(true);
			break;
		    }
		}
	    }
	});
    }
}

class MyJPopupMenu extends JPopupMenu {

    private static final long serialVersionUID = 1L;
    int Count = 0;
    JHelpPanel JP;

    public MyJPopupMenu(final JHelpPanel jp) {
	super();
	JP = jp;
	setFont(new Font("System", 0, 11));
	setFocusable(false);
    }

    public void addI(final String s) {
	Count++;
	add(new MyJPopupMenuItem(JP, s));
    }
}

class MyJTextPane extends JEditorPane implements HyperlinkListener {

    private static final long serialVersionUID = 1L;
    String content;
    HTMLEditorKit editorkit;
    HTMLDocument doc;
    JHelpPanel JP;

    public MyJTextPane(final JHelpPanel jp) {
	super();
	JP = jp;
	this.setContentType("text/html;");
	editorkit = (HTMLEditorKit) getEditorKit();
	doc = (HTMLDocument) getDocument();
	final StyleSheet myStyleSheet = new StyleSheet();
	myStyleSheet.addRule("body {color: #333333;font-family: Verdana, Arial, Helvetica, sans-serif;"
				+ "text-align: justify;font-size: 10px;background-color: #F1F3F9;}");
	myStyleSheet.addRule("ul {text-align: left;list-style-type: disc;padding-left: 15px;list-style-position: outside;}");
	myStyleSheet.addRule(".pfooter {text-align: center;font-size: 10px;padding-bottom: 5px;"
				+ "padding-top: 5px;background-color: #eaedf3;}");
	myStyleSheet.addRule(".pfootertitle {font-weight: bold;text-align: center;font-size: 10px;padding-bottom: 5px;"
				+ "padding-top: 5px;background-color: #DBDDE3;}");
	myStyleSheet.addRule("a {color: #0a9100;text-decoration: none;}");
	myStyleSheet.addRule(".aanchor {color: #CC6633;}");
	myStyleSheet.addRule(".centered {text-align: center;}");
	myStyleSheet.addRule(".comment {text-align: center;font-size: 9px;font-style: oblique;font-weight: normal;}");
	myStyleSheet.addRule(".comment2 {text-align: justify;font-size: 9px;font-style: oblique;font-weight: normal;}");
	myStyleSheet.addRule(".ttle {color: #000000;font-family: Verdana, Arial, Helvetica, sans-serif;"
				+ "text-align: center;font-weight: bold;font-size: 11px;background-color: #DBDDE3;}");
	myStyleSheet.addRule(".tab {border-width: 1px;border-color: #909090;border-style:solid;}");
	myStyleSheet.addRule(".tds {border-width: 1px;border-color: #909090;border-style:solid;background-color: #DBDDE3;}");
	myStyleSheet.addRule(".image {border-width: 1px;border-color: #909090;border-style:solid;background-color: #F1F3F9;}");
	editorkit.setStyleSheet(myStyleSheet);
	this.setEditable(false);
	addHyperlinkListener(this);
    }

    void fixImagePath() {
	final String SP = System.getProperty("file.separator");

	// Check if there are external images (inside the docs folder of
	// carmetal_config directory
	Pattern p = Pattern.compile("(<img [^>]*src=\")(images/)([^\"]+)(\"[^>]*>)", Pattern.CASE_INSENSITIVE);
	Matcher m = p.matcher(content);
	StringBuffer sb = new StringBuffer();

	while (m.find()) {
	    String myst = "<p class=\"centered\">" + m.group(1) + "file:///";
	    myst += Global.getHomeDirectory().replace("\\", "\\\\");
	    myst += "docs/images/".replace("/", SP).replace("\\", "\\\\");
	    myst += m.group(3).replace("/", SP).replace("\\", "\\\\");

	    // on windows, the img tag seems to need the width and height
	    // attribute :
	    final ImageIcon myimg = new ImageIcon(Global.getHomeDirectory() + "docs/images/" + m.group(3));
		if (myimg != null) {
		    myst += "\" width=\"" + myimg.getIconWidth() + "\" height=\"" + myimg.getIconHeight() + "\"";
		}

	    myst += m.group(4);
	    myst += "</p>";
	    m.appendReplacement(sb, myst);
	}
	m.appendTail(sb);
	m.reset();
	content = sb.toString();

	// Check if there are internal images (inside the jar archive with root
	// eric/GUI/icons/ :
	p = Pattern.compile("(<img [^>]*src=\")(#)([^\"]+)(\"[^>]*>)", Pattern.CASE_INSENSITIVE);
	m = p.matcher(content);
	sb = new StringBuffer();
	while (m.find()) {
	    final URL myIMG = Global.getPath("eric/GUI/icons/" + m.group(3));
	    if (myIMG == null)
		continue;
	    String myst = m.group(1);
	    myst += myIMG.toString();
	    myst += m.group(4);
	    m.appendReplacement(sb, myst);
    	}
	m.appendTail(sb);
	m.reset();
	content = sb.toString();
    }

    void setTitle() {
	if (content.equals("")) {
	    return;
	}
	content = content.replace("href=\"#", "class=\"aanchor\" href=\"#");
	final URL myIMG = Global.getPath("eric/GUI/icons/palette/" + JHelpPanel.Subject + ".png");
	final String mytitle = content.substring(0, content.indexOf("<br>"));
	String tag = "";
	if (myIMG == null) {
	    tag = "<table class=\"tab\" width=\"100%\" cellpadding=\"4\" cellspacing=\"0\"><tr border=\"1\">";
	    tag += "<td class=\"ttle\">" + mytitle + "</td>";
	    tag += "</tr></table>";
	} else {
	    tag = "<table class=\"tab\" width=\"100%\" border=\"1\" cellpadding=\"4\" cellspacing=\"0\"><tr>";
	    tag += "<td class=\"ttle\">" + mytitle + "</td>";
	    tag += "<td class=\"ttle\" width=\"40\"><p class=\"image\"><img src=\""+ myIMG.toString() + "\"></p></td>";
	    tag += "</tr></table>";
	}
	content = content.replaceFirst(mytitle, tag);
    }

    void setFooter() {
	final Enumeration e = JP.Related.elements();
	String tag = "";
	while (e.hasMoreElements()) {
	    final String topic = (String) e.nextElement();
	    String topicTitle = "";
	    final Enumeration ev = JP.Other.elements();

	    while (ev.hasMoreElements()) {
		final String s[] = (String[]) ev.nextElement();
		if (s[0].equals(topic)) {
		    topicTitle = s[1];
		    break;
		}
	    }

	    tag += "<tr><td class=\"pfooter\"><a class=\"aanchor\" href=\"#" + topic + "\">" + topicTitle + "</a></td></tr>";
	}

	if (!tag.equals("")) {
	    content += "<table  class=\"tab\" width=\"100%\" border=\"1\" cellpadding=\"4\" cellspacing=\"0\">";
	    content += "<tr><td class=\"pfootertitle\">" + Global.Loc("contexthelp.seealso") + "</tr></td>";
	    content += tag;
	    content += "</table>";
	}
    }

    void setContent(final String s) {
	try {
	    content = s;
	    fixImagePath();
	    setTitle();
	    setFooter();
	    editorkit.insertHTML(doc, doc.getLength(), content, 0, 0, null);
	} catch (final Exception ex) {
	}
	// JP.focusTxt();
    }

    @Override
    public void hyperlinkUpdate(final HyperlinkEvent r) {
	if (r.getEventType() == javax.swing.event.HyperlinkEvent.EventType.ACTIVATED) {
	    String lnk = "";
	    if (r.getDescription().startsWith("#")) {
		lnk = r.getDescription().substring(1);
		JHelpPanel.Subject = lnk;
		JP.clearSearchTxtField();
		JP.fill(true);
		return;
	    } else {
		lnk = r.getDescription();
		if (!lnk.startsWith("http://")) {
		    lnk = "http://" + lnk;
		}
		JBrowserLauncher.openURL(lnk);
		return;
	    }
	}
    }
}