Bug 13036 fixed: Bad help page associated to history browser
[scilab.git] / scilab / modules / history_browser / src / java / org / scilab / modules / history_browser / CommandHistory.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - DIGITEO - Vincent COUVERT
4  * Copyright (C) 2010 - DIGITEO - Allan CORNET
5  * Copyright (C) 2011 - Calixte DENIZET
6  *
7  * This file must be used under the terms of the CeCILL.
8  * This source file is licensed as described in the file COPYING, which
9  * you should have received as part of this distribution.  The terms
10  * are also available at
11  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
12  *
13  */
14
15 package org.scilab.modules.history_browser;
16
17 import java.awt.BorderLayout;
18 import java.awt.Color;
19 import java.awt.Component;
20 import java.awt.Graphics;
21 import java.awt.event.ActionEvent;
22 import java.awt.event.ActionListener;
23 import java.util.ArrayList;
24 import java.util.Collections;
25
26 import javax.swing.BoundedRangeModel;
27 import javax.swing.JPanel;
28 import javax.swing.JScrollBar;
29 import javax.swing.JScrollPane;
30 import javax.swing.JTree;
31 import javax.swing.SwingUtilities;
32 import javax.swing.Timer;
33 import javax.swing.tree.DefaultMutableTreeNode;
34 import javax.swing.tree.DefaultTreeCellRenderer;
35 import javax.swing.tree.DefaultTreeModel;
36 import javax.swing.tree.ExpandVetoException;
37 import javax.swing.tree.TreeModel;
38 import javax.swing.tree.TreeNode;
39 import javax.swing.tree.TreePath;
40
41 import org.scilab.modules.gui.bridge.tab.SwingScilabTab;
42 import org.scilab.modules.gui.bridge.window.SwingScilabWindow;
43 import org.scilab.modules.gui.menu.Menu;
44 import org.scilab.modules.gui.menu.ScilabMenu;
45 import org.scilab.modules.gui.menubar.MenuBar;
46 import org.scilab.modules.gui.menubar.ScilabMenuBar;
47 import org.scilab.modules.gui.messagebox.MessageBox;
48 import org.scilab.modules.gui.messagebox.ScilabMessageBox;
49 import org.scilab.modules.gui.tab.SimpleTab;
50 import org.scilab.modules.gui.tabfactory.ScilabTabFactory;
51 import org.scilab.modules.gui.textbox.ScilabTextBox;
52 import org.scilab.modules.gui.textbox.TextBox;
53 import org.scilab.modules.gui.toolbar.ScilabToolBar;
54 import org.scilab.modules.gui.toolbar.ToolBar;
55 import org.scilab.modules.gui.utils.WindowsConfigurationManager;
56 import org.scilab.modules.gui.window.ScilabWindow;
57 import org.scilab.modules.history_browser.actions.ClearAction;
58 import org.scilab.modules.history_browser.actions.CloseAction;
59 import org.scilab.modules.history_browser.actions.CopyAction;
60 import org.scilab.modules.history_browser.actions.CutAction;
61 import org.scilab.modules.history_browser.actions.DeleteAction;
62 import org.scilab.modules.history_browser.actions.EditInScinotesAction;
63 import org.scilab.modules.history_browser.actions.EvaluateAction;
64 import org.scilab.modules.history_browser.actions.HelpAction;
65 import org.scilab.modules.history_browser.actions.PrefsAction;
66 import org.scilab.modules.history_manager.HistoryManagement;
67 import org.scilab.modules.localization.Messages;
68
69 /**
70  * Main Scilab Command History GUI
71  * @author Vincent COUVERT
72  * @author Calixte DENIZET
73  */
74 @SuppressWarnings(value = { "serial" })
75 public final class CommandHistory extends SwingScilabTab implements SimpleTab {
76
77     public static final String COMMANDHISTORYUUID = "856207f6-0a60-47a0-b9f4-232feedd4bf4";
78
79     private static final int DEFAULT_WIDTH = 450;
80     private static final int DEFAULT_HEIGHT = 550;
81     private static final String NEWLINE = "\n";
82     private static final String SESSION_BEGINNING = "// -- ";
83     private static final String SESSION_ENDING = " -- //";
84
85     private static HistoryTree scilabHistoryTree;
86     private static DefaultMutableTreeNode scilabHistoryRootNode;
87     private static DefaultMutableTreeNode currentSessionNode;
88     private static DefaultTreeModel scilabHistoryTreeModel;
89     private static SwingScilabTab browserTab;
90     private static JScrollPane scrollPane;
91
92     private static boolean modelLoaded;
93     private static boolean initialized;
94
95     private static java.util.List<String> linesToAppend;
96     private static javax.swing.Timer linesToAppendTimer;
97
98     static {
99         ScilabTabFactory.getInstance().addTabFactory(CommandHistoryTabFactory.getInstance());
100
101         linesToAppend = Collections.synchronizedList(new ArrayList<String>());
102         linesToAppendTimer = new Timer(0, new ActionListener() {
103             public void actionPerformed(ActionEvent e) {
104                 appendLinesOnEDT();
105             }
106         });
107         linesToAppendTimer.setRepeats(false);
108     }
109
110     /**
111      * Constructor
112      */
113     private CommandHistory() {
114         super(CommandHistoryMessages.TITLE, COMMANDHISTORYUUID);
115         setAssociatedXMLIDForHelp("browsehistory");
116         initialize();
117         addMenuBar(createMenuBar());
118         addToolBar(createToolBar());
119         addInfoBar(ScilabTextBox.createTextBox());
120
121         scilabHistoryTree.addMouseListener(new CommandHistoryMouseListener());
122
123         DeleteAction.registerKeyAction();
124         EvaluateAction.registerKeyAction();
125         CopyAction.registerKeyAction();
126         CutAction.registerKeyAction();
127         CloseAction.registerKeyAction();
128
129         scrollPane = new JScrollPane(scilabHistoryTree);
130         JPanel contentPane = new JPanel(new BorderLayout());
131         contentPane.add(scrollPane);
132         setContentPane(contentPane);
133     }
134
135     /**
136      * Initialize the History Browser at Scilab launch
137      * Called directly from Scilab
138      */
139     public static void initialize() {
140         if (!initialized) {
141             scilabHistoryRootNode = new DefaultMutableTreeNode(Messages.gettext("History loading in progress..."));
142             scilabHistoryTreeModel = new DefaultTreeModel(scilabHistoryRootNode);
143             scilabHistoryTree = new HistoryTree(scilabHistoryTreeModel);
144             scilabHistoryTree.setShowsRootHandles(true);
145             scilabHistoryTree.setDragEnabled(true);
146             scilabHistoryTree.setEnabled(true);
147             scilabHistoryTree.setRootVisible(false);
148             scilabHistoryTree.setScrollsOnExpand(true);
149             scilabHistoryTree.setVisible(false);
150
151             // Under Windows the directory icon is used: bad....
152             DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) scilabHistoryTree.getCellRenderer();
153             renderer.setLeafIcon(null);
154             renderer.setClosedIcon(null);
155             renderer.setOpenIcon(null);
156
157             initialized = true;
158         }
159     }
160
161     /**
162      * Create a new Command History tab
163      * @return the corresponding tab
164      */
165     public static SwingScilabTab createCommandHistoryTab() {
166         browserTab = new CommandHistory();
167         WindowsConfigurationManager.restorationFinished(browserTab);
168
169         return browserTab;
170     }
171
172     /**
173      * {@inheritDoc}
174      */
175     public void addInfoBar(TextBox infoBarToAdd) {
176         setInfoBar(infoBarToAdd);
177     }
178
179     /**
180      * {@inheritDoc}
181      */
182     public void addMenuBar(MenuBar menuBarToAdd) {
183         setMenuBar(menuBarToAdd);
184     }
185
186     /**
187      * {@inheritDoc}
188      */
189     public void addToolBar(ToolBar toolBarToAdd) {
190         setToolBar(toolBarToAdd);
191     }
192
193     /**
194      * {@inheritDoc}
195      */
196     public SwingScilabWindow getParentWindow() {
197         return SwingScilabWindow.allScilabWindows.get(getParentWindowId());
198     }
199
200     /**
201      * {@inheritDoc}
202      */
203     public SimpleTab getAsSimpleTab() {
204         return this;
205     }
206
207     /**
208      * Update the browser once an history file has been loaded
209      */
210     public static void loadFromFile() {
211         final String historyLines[] = HistoryManagement.getAllLinesOfScilabHistory();
212
213         SwingUtilities.invokeLater(new Runnable() {
214             @Override
215             public void run() {
216                 reset();
217                 int nbEntries = historyLines.length;
218                 for (int entryIndex = 0; entryIndex < nbEntries; entryIndex++) {
219                     /* Do not expand at each insertion for performances reasons */
220                     appendLineAndExpand(historyLines[entryIndex], false);
221                 }
222             }
223         });
224
225         /* Expand all sessions tree */
226         expandAll();
227     }
228
229     public static void expandAll() {
230         if (isHistoryVisible()) {
231             // put the expansion in an invokeLater to avoid some kind of freeze with huge history
232             SwingUtilities.invokeLater(new Runnable() {
233                 public void run() {
234                     scilabHistoryTree.setVisible(true);
235                     if (!modelLoaded) {
236                         scilabHistoryTreeModel.nodeStructureChanged((TreeNode) scilabHistoryTreeModel.getRoot());
237                         modelLoaded = true;
238                     }
239
240                     final Object root = scilabHistoryTreeModel.getRoot();
241                     final TreePath pathRoot = new TreePath(root);
242                     final int N = scilabHistoryTreeModel.getChildCount(root);
243                     scilabHistoryTree.mustFire = false;
244                     for (int i = 0; i < N; i++) {
245                         Object o = scilabHistoryTreeModel.getChild(root, i);
246                         if (!scilabHistoryTreeModel.isLeaf(o)) {
247                             scilabHistoryTree.expandPath(pathRoot.pathByAddingChild(o));
248                         }
249                     }
250                     scilabHistoryTree.mustFire = true;
251                     scilabHistoryTree.fireTreeExpanded(pathRoot);
252
253                     scrollAtBottom();
254                 }
255             });
256         }
257     }
258
259     /**
260      * Add a new line to the History Browser
261      * @param lineToAppend the line to append
262      */
263     public static void appendLine(String lineToAppend) {
264         synchronized (linesToAppend) {
265             linesToAppend.add(lineToAppend);
266             linesToAppendTimer.start();
267         }
268     }
269
270     public static void appendLinesOnEDT() {
271         synchronized (linesToAppend) {
272             for (String lineToAppend : linesToAppend) {
273                 appendLineAndExpand(lineToAppend, true);
274             }
275             linesToAppend.clear();
276         }
277     }
278
279     /**
280      * check if line is a begin session
281      * @param line to check
282      * @retour true or false
283      */
284     private static boolean isBeginSessionLine(String lineToAppend) {
285         if (lineToAppend.startsWith(SESSION_BEGINNING) && lineToAppend.endsWith(SESSION_ENDING)) {
286             return true;
287         }
288         return false;
289     }
290
291     /**
292      * Add a new line to the History Browser
293      * @param lineToAppend the line to append
294      * @param expand do we need to expand all session nodes?
295      */
296     public static void appendLineAndExpand(String lineToAppend, boolean expand) {
297         if (isBeginSessionLine(lineToAppend)) {
298             // Create a new session node
299             currentSessionNode = new DefaultMutableTreeNode(new SessionString(lineToAppend));
300             scilabHistoryTreeModel.insertNodeInto(currentSessionNode, scilabHistoryRootNode, scilabHistoryRootNode.getChildCount());
301             if (expand && isHistoryVisible()) {
302                 scilabHistoryTree.expandRow(scilabHistoryTree.getRowCount() - 1);
303                 scilabHistoryTree.scrollPathToVisible(new TreePath(currentSessionNode.getPath()));
304             }
305         } else {
306             boolean mustScroll = false;
307             if (expand && isHistoryVisible()) {
308                 JScrollBar vb = scrollPane.getVerticalScrollBar();
309                 if (vb != null) {
310                     BoundedRangeModel model = vb.getModel();
311                     // mustScroll is true if the knob is at the bottom of the scollbar.
312                     mustScroll = model.getValue() == model.getMaximum() - model.getExtent();
313                 }
314             }
315             DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(lineToAppend);
316             scilabHistoryTreeModel.insertNodeInto(childNode, currentSessionNode, currentSessionNode.getChildCount());
317             if (expand && isHistoryVisible()) {
318                 scilabHistoryTree.expandRow(scilabHistoryTree.getRowCount() - 1);
319                 if (mustScroll) {
320                     scilabHistoryTree.scrollPathToVisible(new TreePath(childNode.getPath()));
321                 }
322             }
323         }
324     }
325
326     /**
327      * Reset the History Browser (after a clear)
328      */
329     public static void reset() {
330         scilabHistoryRootNode.removeAllChildren();
331         scilabHistoryTreeModel.reload();
332         currentSessionNode = null;
333     }
334
335     /**
336      * Remove an entry from History
337      * @param lineNumber the number of the line
338      */
339     public static void deleteLine(int lineNumber) {
340         int numberOfSessions = scilabHistoryRootNode.getChildCount();
341         int sessionIndex = 0;
342         int numberOfLines = 0;
343
344         while (sessionIndex < numberOfSessions) {
345             if (numberOfLines == lineNumber) {
346                 // Do we try to delete the last session node (which it is the current session used)
347                 if (sessionIndex == (numberOfSessions - 1)) {
348                     // Yes, we forbid it
349                     MessageBox errorMsg = ScilabMessageBox.createMessageBox();
350                     errorMsg.setTitle(CommandHistoryMessages.ERROR);
351                     errorMsg.setMessage(CommandHistoryMessages.CANNOT_DELETE_CURRENT_SESSION_NODE);
352                     errorMsg.setIcon("error");
353                     errorMsg.displayAndWait();
354                     return;
355                 }
356
357                 // Otherwise it is OK, delete session node
358                 TreeNode sessionNode = scilabHistoryRootNode.getChildAt(sessionIndex);
359                 scilabHistoryRootNode.remove(sessionIndex);
360                 scilabHistoryTreeModel.nodesWereRemoved(scilabHistoryRootNode, new int[] {sessionIndex},
361                                                         new Object[] {sessionNode});
362                 break;
363             }
364
365             DefaultMutableTreeNode sessionNode = (DefaultMutableTreeNode) scilabHistoryRootNode.getChildAt(sessionIndex);
366
367             // Did we reach the session containing the line to remove ?
368             if (numberOfLines + sessionNode.getChildCount() >= lineNumber) {
369                 // Yes, delete the child line
370                 int childIndex = lineNumber - numberOfLines - 1;
371                 TreeNode childNode = sessionNode.getChildAt(childIndex);
372                 sessionNode.remove(childIndex);
373                 scilabHistoryTreeModel.nodesWereRemoved(sessionNode, new int[] {childIndex},
374                                                         new Object[] {childNode} );
375                 break;
376             } else {
377                 /* No, jump to next session */
378                 numberOfLines += sessionNode.getChildCount() + 1;
379                 sessionIndex++;
380             }
381         }
382     }
383
384     /**
385      * Close the tab
386      */
387     public static void closeHistory() {
388         browserTab = null;
389     }
390
391     /**
392      * @return the browserTab
393      */
394     public static SwingScilabTab getBrowserTab() {
395         return browserTab;
396     }
397
398     /**
399      * Manage History Browser visibility
400      */
401     public static void setVisible() {
402         if (browserTab == null) {
403             boolean success = WindowsConfigurationManager.restoreUUID(COMMANDHISTORYUUID);
404             if (!success) {
405                 CommandHistoryTabFactory.getInstance().getTab(COMMANDHISTORYUUID);
406                 SwingScilabWindow window = (SwingScilabWindow) ScilabWindow.createWindow().getAsSimpleWindow();
407                 window.addTab(browserTab);
408                 window.setLocation(0, 0);
409                 window.setSize(500, 500);
410                 window.setVisible(true);
411             }
412         }
413         browserTab.setVisible(true);
414         expandAll();
415     }
416
417     /**
418      * Launch the history browser
419      */
420     public static void launchHistoryBrowser() {
421         setVisible();
422     }
423
424     /**
425      * Get History Browser visibility
426      * @return visibility status
427      */
428     private static boolean isHistoryVisible() {
429         return browserTab != null && browserTab.isVisible();
430     }
431
432     /**
433      * Create History Browser MenuBar
434      * @return the menu bar
435      */
436     private static MenuBar createMenuBar() {
437         MenuBar menuBar = ScilabMenuBar.createMenuBar();
438
439         Menu fileMenu = ScilabMenu.createMenu();
440         fileMenu.setText(CommandHistoryMessages.FILE);
441         fileMenu.setMnemonic('F');
442
443         fileMenu.add(CloseAction.createMenuItem());
444
445         menuBar.add(fileMenu);
446
447         Menu editMenu = ScilabMenu.createMenu();
448         editMenu.setText(CommandHistoryMessages.EDIT);
449         editMenu.setMnemonic('E');
450
451         editMenu.add(CopyAction.createMenuItem());
452         editMenu.add(CutAction.createMenuItem());
453         editMenu.add(EvaluateAction.createMenuItem());
454         editMenu.add(EditInScinotesAction.createMenuItem());
455         editMenu.addSeparator();
456         editMenu.add(DeleteAction.createMenuItem());
457         editMenu.add(ClearAction.createMenuItem());
458
459         menuBar.add(editMenu);
460
461         Menu helpMenu = ScilabMenu.createMenu();
462         helpMenu.setText(CommandHistoryMessages.HELP);
463         helpMenu.setMnemonic('?');
464
465         helpMenu.add(HelpAction.createMenuItem());
466
467         menuBar.add(helpMenu);
468
469         return menuBar;
470     }
471
472     /**
473      * Create History Browser ToolBar
474      * @return the tool bar
475      */
476     private static ToolBar createToolBar() {
477         ToolBar toolBar = ScilabToolBar.createToolBar();
478
479         toolBar.add(CopyAction.createPushButton());
480         toolBar.add(CutAction.createPushButton());
481         toolBar.add(DeleteAction.createPushButton());
482
483         toolBar.addSeparator();
484         toolBar.add(PrefsAction.createPushButton());
485         toolBar.addSeparator();
486
487         toolBar.add(HelpAction.createPushButton());
488
489         return toolBar;
490     }
491
492     /**
493      * Get the JTree
494      * @return the tree
495      */
496     public static JTree getTree() {
497         return scilabHistoryTree;
498     }
499
500     /**
501      * Get the JTree Model
502      * @return the tree model
503      */
504     public static DefaultTreeModel getTreeModel() {
505         return scilabHistoryTreeModel;
506     }
507
508     /**
509      * Get the selected commands and store them into an "executable" string
510      * @return the string
511      */
512     public static String getSelectedCommands() {
513         TreePath[] selectedPaths = CommandHistory.getTree().getSelectionPaths();
514
515         if (selectedPaths == null) {
516             return null;
517         }
518
519         String selectedEntries = new String();
520
521         for (int i = 0; i < selectedPaths.length; i++) {
522             Object obj = ((DefaultMutableTreeNode) selectedPaths[i].getLastPathComponent()).getUserObject();
523             selectedEntries += obj.toString();
524
525             if (i < selectedPaths.length - 1) {
526                 selectedEntries += NEWLINE;
527             }
528         }
529
530         return selectedEntries;
531     }
532
533     private static void scrollAtBottom() {
534         SwingUtilities.invokeLater(new Runnable() {
535             public void run() {
536                 scrollPane.getHorizontalScrollBar().setValue(0);
537                 scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
538             }
539         });
540
541     }
542
543     /**
544      * Inner class to render the session nodes in green
545      */
546     static class SessionString {
547
548         String s;
549
550         SessionString(String s) {
551             this.s = s;
552         }
553
554         public String toString() {
555             return s;
556         }
557     }
558
559     @SuppressWarnings(value = { "serial" })
560     static class HistoryTree extends JTree {
561
562         private boolean first = true;
563         private Color defaultColor;
564         private Color sessionColor = new Color(1, 168, 1);
565         boolean mustFire = true;
566
567         HistoryTree(TreeModel model) {
568             super(model);
569             setRowHeight(16);
570             setLargeModel(true);
571
572             setCellRenderer(new DefaultTreeCellRenderer() {
573                 {
574                     defaultColor = getTextNonSelectionColor();
575                 }
576
577                 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
578                     super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
579                     if (((DefaultMutableTreeNode) value).getUserObject() instanceof SessionString) {
580                         setTextNonSelectionColor(sessionColor);
581                     } else {
582                         setTextNonSelectionColor(defaultColor);
583                     }
584
585                     return this;
586                 }
587             });
588         }
589
590         public void fireTreeExpanded(TreePath path) {
591             if (mustFire) {
592                 super.fireTreeExpanded(path);
593             }
594         }
595
596         public void fireTreeWillExpand(TreePath path) throws ExpandVetoException {
597             if (mustFire) {
598                 super.fireTreeWillExpand(path);
599             }
600         }
601
602         public void paint(final Graphics g) {
603             if (first) {
604                 g.setFont(getFont());
605                 int height = g.getFontMetrics().getHeight();
606                 setRowHeight(height);
607                 setLargeModel(true);
608                 first = false;
609                 scrollPane.getVerticalScrollBar().setUnitIncrement(height);
610                 scrollAtBottom();
611             }
612             try {
613                 super.paint(g);
614             } catch (Exception e) {
615                 SwingUtilities.invokeLater(new Runnable() {
616                     public void run() {
617                         paint(g);
618                     }
619                 });
620             }
621         }
622     }
623 }