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