2 * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3 * Copyright (C) 2009 - DIGITEO - Sylvestre Ledru
4 * Copyright (C) 2011 - Calixte DENIZET
6 * This file must be used under the terms of the CeCILL.
7 * This source file is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at
10 * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
13 package org.scilab.modules.gui.bridge.helpbrowser;
15 import java.awt.Component;
16 import java.awt.Container;
17 import java.awt.DefaultFocusTraversalPolicy;
18 import java.awt.event.ActionEvent;
19 import java.awt.event.ActionListener;
20 import java.awt.event.MouseWheelEvent;
21 import java.awt.event.MouseWheelListener;
22 import java.beans.PropertyChangeEvent;
23 import java.beans.PropertyChangeListener;
25 import java.lang.reflect.Field;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Method;
28 import java.net.JarURLConnection;
29 import java.net.MalformedURLException;
31 import java.util.ArrayList;
32 import java.util.Enumeration;
33 import java.util.List;
34 import java.util.regex.Matcher;
36 import javax.help.DefaultHelpHistoryModel;
37 import javax.help.HelpSet;
38 import javax.help.JHelpContentViewer;
39 import javax.help.plaf.basic.BasicContentViewerUI;
40 import javax.swing.AbstractAction;
41 import javax.swing.JComponent;
42 import javax.swing.JEditorPane;
43 import javax.swing.JMenuItem;
44 import javax.swing.JPopupMenu;
45 import javax.swing.JScrollPane;
46 import javax.swing.SwingUtilities;
47 import javax.swing.event.HyperlinkEvent;
48 import javax.swing.text.BadLocationException;
49 import javax.swing.text.DefaultEditorKit;
50 import javax.swing.text.Document;
51 import javax.swing.text.Element;
52 import javax.swing.text.MutableAttributeSet;
53 import javax.swing.text.StyleConstants;
54 import javax.swing.text.StyleContext;
55 import javax.swing.text.html.HTMLDocument;
57 import org.scilab.modules.commons.ScilabConstants;
58 import org.scilab.modules.commons.gui.ScilabKeyStroke;
59 import org.scilab.modules.gui.console.ScilabConsole;
60 import org.scilab.modules.gui.helpbrowser.ScilabHelpBrowser;
61 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
62 import org.scilab.modules.gui.tab.Tab;
63 import org.scilab.modules.gui.utils.ConfigManager;
64 import org.scilab.modules.gui.utils.WebBrowser;
65 import org.scilab.modules.localization.Messages;
68 * This class inherits from BasicContentViewerUI from Javahelp.
69 * Through this class, we are adding some features on the javahelp browser
70 * We are adding a popup menu on the right click of the mouse
71 * In this menu, we are providing:
73 * - Edit in the text editor
78 * @author Sylvestre LEDRU
79 * @author Calixte DENIZET
81 public class SwingScilabHelpBrowserViewer extends BasicContentViewerUI implements MouseWheelListener {
83 private static final String SCILAB_PROTO = "scilab://";
84 private static final String FILE_PROTO = "file://";
85 private static final String SCI = ScilabConstants.SCI.getPath().replaceAll("\\\\", "/");;
86 private static final String SHIFTEQ = "shiftEquals";
87 private static final long serialVersionUID = -2593697956426596790L;
88 private static final int[] fontSizes = new int[]{8, 10, 12, 14, 18, 24, 36};
89 private static final boolean isMac = System.getProperty("os.name").toLowerCase().indexOf("mac") != -1;
91 private static int currentFontSize = ConfigManager.getHelpFontSize();
93 /* This field is a copy of BasicContentViewerUI which is privated.
94 * Therefor, I am changing the permission here to make it available
95 * to the methods of this object
97 private JEditorPane accessibleHtml;
99 private JHelpContentViewer x;
100 private List<HelpSet> helpSets;
102 public SwingScilabHelpBrowserViewer(JHelpContentViewer x) {
107 public static javax.swing.plaf.ComponentUI createUI(JComponent x) {
108 return new SwingScilabHelpBrowserViewer((JHelpContentViewer) x);
112 * @return the JEditorPane used in the HTML view
114 public JEditorPane getAccessibleHTML() {
115 return accessibleHtml;
119 * Update the browser links
121 public void hyperlinkUpdate(HyperlinkEvent event) {
122 if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
123 if (event.getDescription().startsWith("http://")) {
124 WebBrowser.openUrl(event.getURL(), event.getDescription());
125 } else if (event.getDescription().startsWith(SCILAB_PROTO)) {
126 if (helpSets == null) {
127 initHelpSets(x.getModel().getHelpSet());
129 URL url = resolvScilabLink(event);
131 super.hyperlinkUpdate(new HyperlinkEvent(event.getSource(), event.getEventType(), url, ""));
133 } else if (event.getDescription().startsWith(FILE_PROTO)) {
134 String url = event.getDescription();
135 url = url.replaceFirst("SCI", Matcher.quoteReplacement(SCI));
136 WebBrowser.openUrl(url);
138 super.hyperlinkUpdate(event);
143 private void initHelpSets(HelpSet hs) {
144 helpSets = new ArrayList();
146 for (Enumeration<HelpSet> e = hs.getHelpSets(); e.hasMoreElements();) {
147 helpSets.add(e.nextElement());
153 * @param id the id to find
154 * @return the URL corresponding to the id
156 public URL getURLFromID(String id) {
159 for (HelpSet hs : helpSets) {
160 javax.help.Map map = hs.getLocalMap();
161 if (map.isValidID(id, hs)) {
162 url = map.getURLFromID(javax.help.Map.ID.create(id, hs));
168 url = new URL(helpSets.get(0).getHelpSetURL().toString().replace("jhelpset.hs", "ScilabErrorPage.html"));
169 } catch (MalformedURLException ex) { }
175 * Try to find an id in a toolbox
176 * @param tbxName the toolbox's name
177 * @param id the id to find
178 * @return the URL corresponding to the id
180 public URL getURLFromID(String tbxName, String id) {
181 if (tbxName == null) {
182 return getURLFromID(id);
186 for (HelpSet hs : helpSets) {
187 if (hs.getHelpSetURL().toString().replaceAll("\\\\", "/").indexOf("/" + tbxName + "/") != -1) {
188 javax.help.Map map = hs.getLocalMap();
189 if (map.isValidID(id, hs)) {
190 url = map.getURLFromID(javax.help.Map.ID.create(id, hs));
197 url = new URL(helpSets.get(0).getHelpSetURL().toString().replace("jhelpset.hs", "ScilabErrorPage.html"));
198 } catch (MalformedURLException ex) { }
204 * Try to transform an address such as scilab://scilab.help/bvode into a conform URL
205 * pointing to the corresponding file in using jar: protocol.
206 * E.g. scilab://scilab.help/bvode will be transform into
207 * jar:file:SCI/modules/helptools/jar/scilab_fr_FR_help.jar!/scilab_fr_FR_help/bvode.html
208 * (where SCI has the good value)
209 * @param address the address to convert
210 * @return the correct address in using jar:// protocol
212 public URL resolvScilabLink(HyperlinkEvent event) {
213 int pos = SCILAB_PROTO.length();
214 String addr = event.getDescription();
215 addr = addr.trim().replaceAll("\\\\", "/");
216 addr = addr.substring(pos);
218 pos = addr.indexOf("/");
222 location = addr.substring(0, pos);
223 if (pos != addr.length()) {
224 path = addr.substring(pos + 1);
227 return getURLFromID(addr);
230 String[] splitLoc = location.split("\\.");
231 String mainLocation = null;
232 String subLocation = null;
234 if (splitLoc.length >= 1) {
235 mainLocation = splitLoc[0];
237 if (splitLoc.length >= 2) {
238 subLocation = splitLoc[1];
241 if (subLocation.equals("help")) {
242 if (mainLocation.equals("scilab")) {
243 return getURLFromID(path);
245 return getURLFromID(mainLocation, path);
247 } else if (subLocation.equals("xcos") || subLocation.equals("scinotes")) {
248 if (!mainLocation.equals("scilab")) {
249 exec(subLocation, getToolboxPath() + "/" + path);
251 exec(subLocation, SCI + "/modules/" + path);
253 } else if (subLocation.equals("demos")) {
254 if (!mainLocation.equals("scilab")) {
255 exec(getToolboxPath() + "/demos/" + path + ".sce");
257 exec(SCI + "/modules/" + path + ".sce");
259 } else if (subLocation.equals("execexample")) {
260 execExample(event.getSourceElement().getParentElement().getParentElement().getParentElement().getElement(0).getElement(0));
261 } else if (subLocation.equals("editexample")) {
262 editExample(event.getSourceElement().getParentElement().getParentElement().getParentElement().getElement(0).getElement(0));
263 } else if (subLocation.equals("exec")) {
264 if (!mainLocation.equals("scilab")) {
265 exec(getToolboxPath() + "/" + path);
267 exec(SCI + "/modules/" + path);
275 * @return the path of the toolbox
277 public String getToolboxPath() {
279 URL url = ((JarURLConnection) x.getCurrentURL().openConnection()).getJarFileURL();
280 return new File(url.toURI()).getParentFile().getParent();
281 } catch (Exception e) { }
287 * @return the current URL as String being displayed
289 public String getCurrentURL() {
290 return x.getCurrentURL().toString();
294 * Execute the code in example
295 * @param pre the preformatted Element containing Scilab's code
297 public void execExample(Element pre) {
298 String code = getCode(pre);
300 ScilabConsole.getConsole().getAsSimpleConsole().sendCommandsToScilab(code, true /* display */, false /* store in history */);
301 } catch (NoClassDefFoundError e) {
302 ScilabModalDialog.show((Tab) SwingUtilities.getAncestorOfClass(Tab.class, x), Messages.gettext("Feature not available in this mode..."));
307 * Edit the code in example
308 * @param pre the preformatted Element containing Scilab's code
310 public static void editExample(Element pre) {
316 * @param code the code to edit
318 private static void edit(String code) {
320 /* Dynamic load of the SciNotes class.
321 * This is done to avoid a cyclic dependency on gui <=> scinotes
323 Class scinotesClass = Class.forName("org.scilab.modules.scinotes.SciNotes");
324 Class[] arguments = new Class[] {String.class};
325 Method method = scinotesClass.getMethod("scinotesWithText", arguments);
326 method.invoke(scinotesClass, new Object[]{code});
328 } catch (ClassNotFoundException e) {
329 System.err.println("Could not find SciNotes class");
331 } catch (SecurityException e) {
332 System.err.println("Security error: Could not access to SciNotes class");
334 } catch (NoSuchMethodException e) {
335 System.err.println("Could not access to scinotesWithText method from object SciNotes");
337 } catch (IllegalArgumentException e) {
338 System.err.println("Wrong argument used with scinotesWithText method from object SciNotes");
340 } catch (IllegalAccessException e) {
341 System.err.println("Illegal access with scinotesWithText method from object SciNotes");
343 } catch (InvocationTargetException e) {
344 System.err.println("Error of invocation with scinotesWithText method from object SciNotes");
350 * @param pre the preformatted Element containing Scilab's code
353 private static String getCode(Element pre) {
354 int size = pre.getElementCount();
355 Document doc = pre.getDocument();
356 StringBuilder buffer = new StringBuilder();
357 for (int i = 0; i < size; i++) {
358 Element line = pre.getElement(i);
359 int ssize = line.getElementCount();
360 for (int j = 0; j < ssize; j++) {
361 Element content = line.getElement(j);
362 if (content.isLeaf()) {
364 buffer.append(doc.getText(content.getStartOffset(), content.getEndOffset() - content.getStartOffset()));
365 } catch (BadLocationException e) { }
370 return buffer.toString().trim();
374 * Execute a file given by its path
375 * @param the file path
377 public void exec(String path) {
378 String cmd = "exec('" + path + "', -1)";
380 ScilabConsole.getConsole().getAsSimpleConsole().sendCommandsToScilab(cmd, true, false);
381 } catch (NoClassDefFoundError e) {
382 ScilabModalDialog.show((Tab) SwingUtilities.getAncestorOfClass(Tab.class, x), Messages.gettext("Feature not available in this mode..."));
387 * Execute with the command and a file given by its path
388 * @param command the command to execute
389 * @param the file path
391 public void exec(String command, String path) {
392 String cmd = command + "('" + path + "')";
394 ScilabConsole.getConsole().getAsSimpleConsole().sendCommandsToScilab(cmd, false, false);
395 } catch (NoClassDefFoundError e) {
396 ScilabModalDialog.show((Tab) SwingUtilities.getAncestorOfClass(Tab.class, x), Messages.gettext("Feature not available in this mode..."));
401 * Create the UI interface
402 * @see javax.help.plaf.basic.BasicContentViewerUI#installUI(javax.swing.JComponent)
403 * @param c The component
405 public void installUI(JComponent c) {
407 this.retrievePrivateFieldFromBasicContentViewerUI();
408 this.createPopupMenu(c);
412 * Retrieve the field "html" from BasicContentViewerUI and change
413 * permission (it is private by default)
415 private void retrievePrivateFieldFromBasicContentViewerUI() {
416 Field privateField = null;
418 privateField = BasicContentViewerUI.class.getDeclaredField("html");
419 privateField.setAccessible(true);
420 } catch (SecurityException e) {
421 System.err.println("Security error: Could not change the accessibility on the html component of the help browser.");
422 System.err.println("Please submit a bug report: http://bugzilla.scilab.org");
424 } catch (NoSuchFieldException e) {
425 System.err.println("Could not find the field of the html component of the help browser.");
426 System.err.println("Please submit a bug report: http://bugzilla.scilab.org");
431 this.accessibleHtml = (javax.swing.JEditorPane) privateField.get(this);
432 accessibleHtml.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
433 public void propertyChange(java.beans.PropertyChangeEvent evt) {
434 // Crappy workaround to avoid bad html display (the icons play and edit can be misplaced)
435 // To improve... (it doesn't always work)
436 if (evt.getPropertyName().equals("document")) {
437 accessibleHtml.setVisible(false);
438 accessibleHtml.validate();
440 if (evt.getPropertyName().equals("page")) {
441 if (!accessibleHtml.isVisible()) {
443 SwingUtilities.invokeLater(new Runnable() {
445 accessibleHtml.setVisible(true);
453 // The previous workaround hides the component accessibleHtml
454 // and consequently the focus is given to an other component.
455 // So we force the accessibleHtml to keep the focus.
456 accessibleHtml.setFocusTraversalPolicy(new DefaultFocusTraversalPolicy() {
457 public Component getFirstComponent(Container aContainer) {
461 accessibleHtml.setFocusCycleRoot(true);
463 accessibleHtml.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ScilabKeyStroke.getKeyStroke("OSSCKEY shift EQUALS"), SHIFTEQ);
464 accessibleHtml.getActionMap().put(SHIFTEQ, new AbstractAction() {
465 public void actionPerformed(ActionEvent e) {
466 SwingScilabHelpBrowserViewer.this.increaseFont();
469 SwingUtilities.getAncestorOfClass(JScrollPane.class, accessibleHtml).addMouseWheelListener(this);
470 } catch (IllegalArgumentException e) {
471 System.err.println("Illegal argument in the retrieval of the html component of Javahelp");
473 } catch (IllegalAccessException e) {
474 System.err.println("Illegal access in the retrieval of the html component of Javahelp");
480 * Create the popup menu on the help
481 * @param c The graphic component
483 private void createPopupMenu(JComponent c) {
484 final JPopupMenu popup = new JPopupMenu();
486 JMenuItem menuItem = null;
488 /* Execute into Scilab */
489 ActionListener actionListenerExecuteIntoScilab = new ActionListener() {
490 public void actionPerformed(ActionEvent actionEvent) {
491 String selection = accessibleHtml.getSelectedText();
492 if (selection == null) {
493 ScilabHelpBrowser.getHelpBrowser().getInfoBar().setText(Messages.gettext("No text selected"));
495 ScilabConsole.getConsole().getAsSimpleConsole().sendCommandsToScilab(selection, true /* display */, true /* store in history */);
499 menuItem = new JMenuItem(Messages.gettext("Execute into Scilab"));
500 menuItem.addActionListener(actionListenerExecuteIntoScilab);
501 if (!ScilabConsole.isExistingConsole()) { /* Only available in STD mode */
502 menuItem.setEnabled(false);
507 /* Edit in the Scilab Text Editor */
508 ActionListener actionListenerLoadIntoTextEditor = new ActionListener() {
509 public void actionPerformed(ActionEvent actionEvent) {
510 String selection = accessibleHtml.getSelectedText();
511 if (selection == null) {
512 ScilabHelpBrowser.getHelpBrowser().getInfoBar().setText(Messages.gettext("No text selected"));
519 menuItem = new JMenuItem(Messages.gettext("Edit in the Scilab Text Editor"));
521 Class scinotesClass = Class.forName("org.scilab.modules.scinotes.SciNotes");
522 } catch (ClassNotFoundException e) {
523 /* SciNotes not available */
524 menuItem.setEnabled(false);
526 menuItem.addActionListener(actionListenerLoadIntoTextEditor);
528 popup.addSeparator();
530 /* Back in the history*/
531 ActionListener actionListenerBackHistory = new ActionListener() {
532 public void actionPerformed(ActionEvent actionEvent) {
533 DefaultHelpHistoryModel history = SwingScilabHelpBrowser.getHelpHistory();
534 /* Not at the first position */
535 if (history.getIndex() > 0) {
536 SwingScilabHelpBrowser.getHelpHistory().goBack();
541 menuItem = new JMenuItem(Messages.gettext("Back"));
542 menuItem.addActionListener(actionListenerBackHistory);
545 /* Forward in the history*/
546 ActionListener actionListenerForwardHistory = new ActionListener() {
547 public void actionPerformed(ActionEvent actionEvent) {
548 DefaultHelpHistoryModel history = SwingScilabHelpBrowser.getHelpHistory();
549 /* Not at the last position */
550 if (history.getHistory().size() != (history.getIndex() + 1)) {
551 SwingScilabHelpBrowser.getHelpHistory().goForward();
556 menuItem = new JMenuItem(Messages.gettext("Forward"));
557 menuItem.addActionListener(actionListenerForwardHistory);
559 popup.addSeparator();
562 menuItem = new JMenuItem(new DefaultEditorKit.CopyAction());
563 menuItem.setText(Messages.gettext("Copy"));
565 popup.addSeparator();
568 ActionListener actionListenerSelectAll = new ActionListener() {
569 public void actionPerformed(ActionEvent actionEvent) {
570 accessibleHtml.selectAll();
573 menuItem = new JMenuItem(Messages.gettext("Select All"));
574 menuItem.addActionListener(actionListenerSelectAll);
577 /* Edit in the Scilab Text Editor */
578 final JMenuItem helpMenuItem = new JMenuItem("Help on the selected text");
580 ActionListener actionListenerHelpOnKeyword= new ActionListener() {
581 public void actionPerformed(ActionEvent actionEvent) {
582 String selection = accessibleHtml.getSelectedText();
583 if (selection == null) {
584 ScilabHelpBrowser.getHelpBrowser().getInfoBar().setText(Messages.gettext("No text selected"));
586 ScilabHelpBrowser.getHelpBrowser().searchKeywork(selection);
590 PropertyChangeListener listenerTextItem = new PropertyChangeListener() {
591 public void propertyChange(PropertyChangeEvent arg0) {
592 String keyword = accessibleHtml.getSelectedText();
593 if (keyword == null) {
594 helpMenuItem.setText(Messages.gettext("Help about a selected text"));
596 int nbOfDisplayedOnlyXChar = 10;
597 if (keyword.length() > nbOfDisplayedOnlyXChar) {
598 keyword = keyword.substring(0, nbOfDisplayedOnlyXChar) + "...";
600 helpMenuItem.setText(Messages.gettext("Help about '") +keyword+"'");
604 helpMenuItem.addPropertyChangeListener(listenerTextItem);
605 helpMenuItem.addActionListener(actionListenerHelpOnKeyword);
606 popup.add(helpMenuItem);
608 /* Creates the Popupmenu on the component */
609 accessibleHtml.setComponentPopupMenu(popup);
615 public void mouseWheelMoved(MouseWheelEvent e) {
616 if ((isMac && e.isMetaDown()) || e.isControlDown()) {
617 int n = e.getWheelRotation();
618 if (currentFontSize != Math.min(Math.max(0, currentFontSize + n), 6)) {
620 ConfigManager.setHelpFontSize(currentFontSize);
627 * Modify the current base font size
628 * @param s the size to add to the current size
630 public void modifyFont(final int s) {
631 SwingUtilities.invokeLater(new Runnable() {
634 HTMLDocument doc = (HTMLDocument) accessibleHtml.getDocument();
635 StyleContext.NamedStyle style = (StyleContext.NamedStyle) doc.getStyleSheet().getStyle("body");
636 MutableAttributeSet attr = (MutableAttributeSet) style.getResolveParent();
637 currentFontSize = Math.min(Math.max(0, currentFontSize + s), 6);
638 StyleConstants.setFontSize(attr, fontSizes[currentFontSize]);
639 style.setResolveParent(attr);
640 } catch (NullPointerException e) {
641 // Can occur if the user is changing quickly the document
648 * Increase the font size +1
650 public void increaseFont() {
651 if (currentFontSize != Math.min(Math.max(0, currentFontSize + 1), 6)) {
653 ConfigManager.setHelpFontSize(currentFontSize);
658 * Decrease the font size -1
660 public void decreaseFont() {
661 if (currentFontSize != Math.min(Math.max(0, currentFontSize - 1), 6)) {
663 ConfigManager.setHelpFontSize(currentFontSize);