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