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