Docking restoration: work in progress... (near the end)
[scilab.git] / scilab / modules / gui / src / java / org / scilab / modules / gui / bridge / helpbrowser / SwingScilabHelpBrowserViewer.java
1 /*
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
5  *
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
11  *
12  */
13 package org.scilab.modules.gui.bridge.helpbrowser;
14
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;
24 import java.io.File;
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;
30 import java.net.URL;
31 import java.util.ArrayList;
32 import java.util.Enumeration;
33 import java.util.List;
34 import java.util.regex.Matcher;
35
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;
56
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;
66
67 /**
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:
72  *  - Execute in Scilab
73  *  - Edit in the text editor
74  *  - Copy
75  *  - Select all
76  *  - History
77  *
78  * @author Sylvestre LEDRU
79  * @author Calixte DENIZET
80  */
81 public class SwingScilabHelpBrowserViewer extends BasicContentViewerUI implements MouseWheelListener {
82
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;
90
91     private static int currentFontSize = ConfigManager.getHelpFontSize();
92
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
96      */
97     private JEditorPane accessibleHtml;
98
99     private JHelpContentViewer x;
100     private List<HelpSet> helpSets;
101
102     public SwingScilabHelpBrowserViewer(JHelpContentViewer x) {
103         super(x);
104         this.x = x;
105     }
106
107     public static javax.swing.plaf.ComponentUI createUI(JComponent x) {
108         return new SwingScilabHelpBrowserViewer((JHelpContentViewer) x);
109     }
110
111     /**
112      * @return the JEditorPane used in the HTML view
113      */
114     public JEditorPane getAccessibleHTML() {
115         return accessibleHtml;
116     }
117
118     /**
119      * Update the browser links
120      */
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());
128                 }
129                 URL url = resolvScilabLink(event);
130                 if (url != null) {
131                     super.hyperlinkUpdate(new HyperlinkEvent(event.getSource(), event.getEventType(), url, ""));
132                 }
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);
137             } else {
138                 super.hyperlinkUpdate(event);
139             }
140         }
141     }
142
143     private void initHelpSets(HelpSet hs) {
144         helpSets = new ArrayList();
145         helpSets.add(hs);
146         for (Enumeration<HelpSet> e = hs.getHelpSets(); e.hasMoreElements();) {
147             helpSets.add(e.nextElement());
148         }
149     }
150
151     /**
152      * Try to find an id
153      * @param id the id to find
154      * @return the URL corresponding to the id
155      */
156     public URL getURLFromID(String id) {
157         URL url = null;
158         try {
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));
163                     if (url != null) {
164                         return url;
165                     }
166                 }
167             }
168             url = new URL(helpSets.get(0).getHelpSetURL().toString().replace("jhelpset.hs", "ScilabErrorPage.html"));
169         } catch (MalformedURLException ex) { }
170
171         return url;
172     }
173
174     /**
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
179      */
180     public URL getURLFromID(String tbxName, String id) {
181         if (tbxName == null) {
182             return getURLFromID(id);
183         }
184         URL url = null;
185         try {
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));
191                         if (url != null) {
192                             return url;
193                         }
194                     }
195                 }
196             }
197             url = new URL(helpSets.get(0).getHelpSetURL().toString().replace("jhelpset.hs", "ScilabErrorPage.html"));
198         } catch (MalformedURLException ex) { }
199
200         return url;
201     }
202
203     /**
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
211      **/
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);
217
218         pos = addr.indexOf("/");
219         String location;
220         String path = "";
221         if (pos != -1) {
222             location = addr.substring(0, pos);
223             if (pos != addr.length()) {
224                 path = addr.substring(pos + 1);
225             }
226         } else {
227             return getURLFromID(addr);
228         }
229
230         String[] splitLoc = location.split("\\.");
231         String mainLocation = null;
232         String subLocation = null;
233
234         if (splitLoc.length >= 1) {
235             mainLocation = splitLoc[0];
236         }
237         if (splitLoc.length >= 2) {
238             subLocation = splitLoc[1];
239         }
240
241         if (subLocation.equals("help")) {
242             if (mainLocation.equals("scilab")) {
243                 return getURLFromID(path);
244             } else {
245                 return getURLFromID(mainLocation, path);
246             }
247         } else if (subLocation.equals("xcos") || subLocation.equals("scinotes")) {
248             if (!mainLocation.equals("scilab")) {
249                 exec(subLocation, getToolboxPath() + "/" + path);
250             } else {
251                 exec(subLocation, SCI + "/modules/" + path);
252             }
253         } else if (subLocation.equals("demos")) {
254             if (!mainLocation.equals("scilab")) {
255                 exec(getToolboxPath() + "/demos/" + path + ".sce");
256             } else {
257                 exec(SCI + "/modules/" + path + ".sce");
258             }
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);
266             } else {
267                 exec(SCI + "/modules/" + path);
268             }
269         }
270
271         return null;
272     }
273
274     /**
275      * @return the path of the toolbox
276      */
277     public String getToolboxPath() {
278         try {
279             URL url = ((JarURLConnection) x.getCurrentURL().openConnection()).getJarFileURL();
280             return new File(url.toURI()).getParentFile().getParent();
281         } catch (Exception e) { }
282
283         return "";
284     }
285
286     /**
287      * @return the current URL as String being displayed
288      */
289     public String getCurrentURL() {
290         return x.getCurrentURL().toString();
291     }
292
293     /**
294      * Execute the code in example
295      * @param pre the preformatted Element containing Scilab's code
296      */
297     public void execExample(Element pre) {
298         String code = getCode(pre);
299         try {
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..."));
303         }
304     }
305
306     /**
307      * Edit the code in example
308      * @param pre the preformatted Element containing Scilab's code
309      */
310     public static void editExample(Element pre) {
311         edit(getCode(pre));
312     }
313
314     /**
315      * Edit the code
316      * @param code the code to edit
317      */
318     private static void edit(String code) {
319         try {
320             /* Dynamic load of the SciNotes class.
321              * This is done to avoid a cyclic dependency on gui <=> scinotes
322              */
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});
327
328         } catch (ClassNotFoundException e) {
329             System.err.println("Could not find SciNotes class");
330             e.printStackTrace();
331         } catch (SecurityException e) {
332             System.err.println("Security error: Could not access to SciNotes class");
333             e.printStackTrace();
334         } catch (NoSuchMethodException e) {
335             System.err.println("Could not access to scinotesWithText method from object SciNotes");
336             e.printStackTrace();
337         } catch (IllegalArgumentException e) {
338             System.err.println("Wrong argument used with scinotesWithText method from object SciNotes");
339             e.printStackTrace();
340         } catch (IllegalAccessException e) {
341             System.err.println("Illegal access with scinotesWithText method from object SciNotes");
342             e.printStackTrace();
343         } catch (InvocationTargetException e) {
344             System.err.println("Error of invocation with scinotesWithText method from object SciNotes");
345             e.printStackTrace();
346         }
347     }
348
349     /**
350      * @param pre the preformatted Element containing Scilab's code
351      * @return the code
352      */
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()) {
363                     try {
364                         buffer.append(doc.getText(content.getStartOffset(), content.getEndOffset() - content.getStartOffset()));
365                     } catch (BadLocationException e) { }
366                 }
367             }
368         }
369
370         return buffer.toString().trim();
371     }
372
373     /**
374      * Execute a file given by its path
375      * @param the file path
376      */
377     public void exec(String path) {
378         String cmd = "exec('" + path + "', -1)";
379         try {
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..."));
383         }
384     }
385
386     /**
387      * Execute with the command and a file given by its path
388      * @param command the command to execute
389      * @param the file path
390      */
391     public void exec(String command, String path) {
392         String cmd = command + "('" + path + "')";
393         try {
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..."));
397         }
398     }
399
400     /**
401      * Create the UI interface
402      * @see javax.help.plaf.basic.BasicContentViewerUI#installUI(javax.swing.JComponent)
403      * @param c The component
404      */
405     public void installUI(JComponent c) {
406         super.installUI(c);
407         this.retrievePrivateFieldFromBasicContentViewerUI();
408         this.createPopupMenu(c);
409     }
410
411     /**
412      * Retrieve the field "html" from BasicContentViewerUI and change
413      * permission (it is private by default)
414      */
415     private void retrievePrivateFieldFromBasicContentViewerUI() {
416         Field privateField = null;
417         try {
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");
423             e.printStackTrace();
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");
427             e.printStackTrace();
428         }
429
430         try {
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();
439                         }
440                         if (evt.getPropertyName().equals("page")) {
441                             if (!accessibleHtml.isVisible()) {
442                                 modifyFont(0);
443                                 SwingUtilities.invokeLater(new Runnable() {
444                                         public void run() {
445                                             accessibleHtml.setVisible(true);
446                                         }
447                                     });
448                             }
449                         }
450                     }
451                 });
452
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) {
458                         return x;
459                     }
460                 });
461             accessibleHtml.setFocusCycleRoot(true);
462
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();
467                     }
468                 });
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");
472             e.printStackTrace();
473         } catch (IllegalAccessException e) {
474             System.err.println("Illegal access in the retrieval of the html component of Javahelp");
475             e.printStackTrace();
476         }
477     }
478
479     /**
480      * Create the popup menu on the help
481      * @param c The graphic component
482      */
483     private void createPopupMenu(JComponent c) {
484         final JPopupMenu popup = new JPopupMenu();
485
486         JMenuItem menuItem = null;
487
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"));
494                     } else {
495                         ScilabConsole.getConsole().getAsSimpleConsole().sendCommandsToScilab(selection, true /* display */, true /* store in history */);
496                     }
497                 }
498             };
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);
503         }
504         popup.add(menuItem);
505
506
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"));
513                     } else {
514                         edit(selection);
515                     }
516                 }
517             };
518
519         menuItem = new JMenuItem(Messages.gettext("Edit in the Scilab Text Editor"));
520         try {
521             Class scinotesClass = Class.forName("org.scilab.modules.scinotes.SciNotes");
522         } catch (ClassNotFoundException e) {
523             /* SciNotes not available */
524             menuItem.setEnabled(false);
525         }
526         menuItem.addActionListener(actionListenerLoadIntoTextEditor);
527         popup.add(menuItem);
528         popup.addSeparator();
529
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();
537                     }
538                 }
539             };
540
541         menuItem = new JMenuItem(Messages.gettext("Back"));
542         menuItem.addActionListener(actionListenerBackHistory);
543         popup.add(menuItem);
544
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();
552                     }
553                 }
554             };
555
556         menuItem = new JMenuItem(Messages.gettext("Forward"));
557         menuItem.addActionListener(actionListenerForwardHistory);
558         popup.add(menuItem);
559         popup.addSeparator();
560
561         /* Copy */
562         menuItem = new JMenuItem(new DefaultEditorKit.CopyAction());
563         menuItem.setText(Messages.gettext("Copy"));
564         popup.add(menuItem);
565         popup.addSeparator();
566
567         /* Select all */
568         ActionListener actionListenerSelectAll = new ActionListener() {
569                 public void actionPerformed(ActionEvent actionEvent) {
570                     accessibleHtml.selectAll();
571                 }
572             };
573         menuItem = new JMenuItem(Messages.gettext("Select All"));
574         menuItem.addActionListener(actionListenerSelectAll);
575         popup.add(menuItem);
576
577         /* Edit in the Scilab Text Editor */
578         final JMenuItem helpMenuItem = new JMenuItem("Help on the selected text");
579
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"));
585                     } else {
586                         ScilabHelpBrowser.getHelpBrowser().searchKeywork(selection);
587                     }
588                 }
589             };
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"));
595                     } else {
596                         int nbOfDisplayedOnlyXChar = 10;
597                         if (keyword.length() > nbOfDisplayedOnlyXChar) {
598                             keyword = keyword.substring(0, nbOfDisplayedOnlyXChar) + "...";
599                         }
600                         helpMenuItem.setText(Messages.gettext("Help about '") +keyword+"'");
601                     }
602                 }
603             };
604         helpMenuItem.addPropertyChangeListener(listenerTextItem);
605         helpMenuItem.addActionListener(actionListenerHelpOnKeyword);
606         popup.add(helpMenuItem);
607
608         /* Creates the Popupmenu on the component */
609         accessibleHtml.setComponentPopupMenu(popup);
610     }
611
612     /**
613      * {@inheritedDoc}
614      */
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)) {
619                 modifyFont(n);
620                 ConfigManager.setHelpFontSize(currentFontSize);
621             }
622             e.consume();
623         }
624     }
625
626     /**
627      * Modify the current base font size
628      * @param s the size to add to the current size
629      */
630     public void modifyFont(final int s) {
631         SwingUtilities.invokeLater(new Runnable() {
632                 public void run() {
633                     try {
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
642                     }
643                 }
644             });
645     }
646
647     /**
648      * Increase the font size +1
649      */
650     public void increaseFont() {
651         if (currentFontSize != Math.min(Math.max(0, currentFontSize + 1), 6)) {
652             modifyFont(1);
653             ConfigManager.setHelpFontSize(currentFontSize);
654         }
655     }
656
657     /**
658      * Decrease the font size -1
659      */
660     public void decreaseFont() {
661         if (currentFontSize != Math.min(Math.max(0, currentFontSize - 1), 6)) {
662             modifyFont(-1);
663             ConfigManager.setHelpFontSize(currentFontSize);
664         }
665     }
666 }