* Bug #11497 fixed - A lot of EDT violation was detected by the
[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 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
167         return browserTab;
168     }
169
170     /**
171      * {@inheritDoc}
172      */
173     public void addInfoBar(TextBox infoBarToAdd) {
174         setInfoBar(infoBarToAdd);
175     }
176
177     /**
178      * {@inheritDoc}
179      */
180     public void addMenuBar(MenuBar menuBarToAdd) {
181         setMenuBar(menuBarToAdd);
182     }
183
184     /**
185      * {@inheritDoc}
186      */
187     public void addToolBar(ToolBar toolBarToAdd) {
188         setToolBar(toolBarToAdd);
189     }
190
191     /**
192      * {@inheritDoc}
193      */
194     public SwingScilabWindow getParentWindow() {
195         return SwingScilabWindow.allScilabWindows.get(getParentWindowId());
196     }
197
198     /**
199      * {@inheritDoc}
200      */
201     public SimpleTab getAsSimpleTab() {
202         return this;
203     }
204
205     /**
206      * Update the browser once an history file has been loaded
207      */
208     public static void loadFromFile() {
209         final String historyLines[] = HistoryManagement.getAllLinesOfScilabHistory();
210
211         SwingUtilities.invokeLater(new Runnable() {
212             @Override
213             public void run() {
214                 reset();
215                 int nbEntries = historyLines.length;
216                 for (int entryIndex = 0; entryIndex < nbEntries; entryIndex++) {
217                     /* Do not expand at each insertion for performances reasons */
218                     appendLineAndExpand(historyLines[entryIndex], false);
219                 }
220             }
221         });
222
223         /* Expand all sessions tree */
224         expandAll();
225     }
226
227     public static void expandAll() {
228         if (isHistoryVisible()) {
229             // put the expansion in an invokeLater to avoid some kind of freeze with huge history
230             SwingUtilities.invokeLater(new Runnable() {
231                 public void run() {
232                     scilabHistoryTree.setVisible(true);
233                     if (!modelLoaded) {
234                         scilabHistoryTreeModel.nodeStructureChanged((TreeNode) scilabHistoryTreeModel.getRoot());
235                         modelLoaded = true;
236                     }
237
238                     final Object root = scilabHistoryTreeModel.getRoot();
239                     final TreePath pathRoot = new TreePath(root);
240                     final int N = scilabHistoryTreeModel.getChildCount(root);
241                     scilabHistoryTree.mustFire = false;
242                     for (int i = 0; i < N; i++) {
243                         Object o = scilabHistoryTreeModel.getChild(root, i);
244                         if (!scilabHistoryTreeModel.isLeaf(o)) {
245                             scilabHistoryTree.expandPath(pathRoot.pathByAddingChild(o));
246                         }
247                     }
248                     scilabHistoryTree.mustFire = true;
249                     scilabHistoryTree.fireTreeExpanded(pathRoot);
250
251                     WindowsConfigurationManager.restorationFinished(getBrowserTab());
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         while (sessionIndex < numberOfSessions) {
343             if (numberOfLines == lineNumber) {
344                 if (sessionIndex == (numberOfSessions - 1)) {
345                     /* Can not remove current session node */
346                     MessageBox errorMsg = ScilabMessageBox.createMessageBox();
347                     errorMsg.setTitle(CommandHistoryMessages.ERROR);
348                     errorMsg.setMessage(CommandHistoryMessages.CANNOT_DELETE_CURRENT_SESSION_NODE);
349                     errorMsg.setIcon("error");
350                     errorMsg.displayAndWait();
351                     return;
352                 }
353                 scilabHistoryRootNode.remove(sessionIndex);
354                 scilabHistoryTreeModel.nodeStructureChanged((TreeNode) scilabHistoryTreeModel.getRoot());
355                 break;
356             }
357
358             /* Session line */
359             numberOfLines++;
360
361             if (numberOfLines + scilabHistoryRootNode.getChildAt(sessionIndex).getChildCount() > lineNumber) {
362                 /* The line has to be remove in current session */
363                 ((DefaultMutableTreeNode) scilabHistoryRootNode.getChildAt(sessionIndex)).remove(lineNumber - numberOfLines);
364                 scilabHistoryTreeModel.nodeStructureChanged(scilabHistoryRootNode.getChildAt(sessionIndex));
365                 expandAll();
366                 break;
367             } else {
368                 /* An other session */
369                 numberOfLines += scilabHistoryRootNode.getChildAt(sessionIndex).getChildCount();
370                 sessionIndex++;
371             }
372         }
373     }
374
375     /**
376      * Close the tab
377      */
378     public static void closeHistory() {
379         browserTab = null;
380     }
381
382     /**
383      * @return the browserTab
384      */
385     public static SwingScilabTab getBrowserTab() {
386         return browserTab;
387     }
388
389     /**
390      * Manage History Browser visibility
391      */
392     public static void setVisible() {
393         if (browserTab == null) {
394             boolean success = WindowsConfigurationManager.restoreUUID(COMMANDHISTORYUUID);
395             if (!success) {
396                 CommandHistoryTabFactory.getInstance().getTab(COMMANDHISTORYUUID);
397                 SwingScilabWindow window = (SwingScilabWindow) ScilabWindow.createWindow().getAsSimpleWindow();
398                 window.addTab(browserTab);
399                 window.setLocation(0, 0);
400                 window.setSize(500, 500);
401                 window.setVisible(true);
402             }
403         }
404         browserTab.setVisible(true);
405         expandAll();
406     }
407
408     /**
409      * Launch the history browser
410      */
411     public static void launchHistoryBrowser() {
412         setVisible();
413     }
414
415     /**
416      * Get History Browser visibility
417      * @return visibility status
418      */
419     private static boolean isHistoryVisible() {
420         return browserTab != null && browserTab.isVisible();
421     }
422
423     /**
424      * Create History Browser MenuBar
425      * @return the menu bar
426      */
427     private static MenuBar createMenuBar() {
428         MenuBar menuBar = ScilabMenuBar.createMenuBar();
429
430         Menu fileMenu = ScilabMenu.createMenu();
431         fileMenu.setText(CommandHistoryMessages.FILE);
432         fileMenu.setMnemonic('F');
433
434         fileMenu.add(CloseAction.createMenuItem());
435
436         menuBar.add(fileMenu);
437
438         Menu editMenu = ScilabMenu.createMenu();
439         editMenu.setText(CommandHistoryMessages.EDIT);
440         editMenu.setMnemonic('E');
441
442         editMenu.add(CopyAction.createMenuItem());
443         editMenu.add(CutAction.createMenuItem());
444         editMenu.add(EvaluateAction.createMenuItem());
445         editMenu.add(EditInScinotesAction.createMenuItem());
446         editMenu.addSeparator();
447         editMenu.add(DeleteAction.createMenuItem());
448         editMenu.add(ClearAction.createMenuItem());
449
450         menuBar.add(editMenu);
451
452         Menu helpMenu = ScilabMenu.createMenu();
453         helpMenu.setText(CommandHistoryMessages.HELP);
454         helpMenu.setMnemonic('?');
455
456         helpMenu.add(HelpAction.createMenuItem());
457
458         menuBar.add(helpMenu);
459
460         return menuBar;
461     }
462
463     /**
464      * Create History Browser ToolBar
465      * @return the tool bar
466      */
467     private static ToolBar createToolBar() {
468         ToolBar toolBar = ScilabToolBar.createToolBar();
469
470         toolBar.add(CopyAction.createPushButton());
471         toolBar.add(CutAction.createPushButton());
472         toolBar.add(DeleteAction.createPushButton());
473
474         toolBar.addSeparator();
475
476         toolBar.add(HelpAction.createPushButton());
477
478         return toolBar;
479     }
480
481     /**
482      * Get the JTree
483      * @return the tree
484      */
485     public static JTree getTree() {
486         return scilabHistoryTree;
487     }
488
489     /**
490      * Get the JTree Model
491      * @return the tree model
492      */
493     public static DefaultTreeModel getTreeModel() {
494         return scilabHistoryTreeModel;
495     }
496
497     /**
498      * Get the selected commands and store them into an "executable" string
499      * @return the string
500      */
501     public static String getSelectedCommands() {
502         TreePath[] selectedPaths = CommandHistory.getTree().getSelectionPaths();
503
504         if (selectedPaths == null) {
505             return null;
506         }
507
508         String selectedEntries = new String();
509
510         for (int i = 0; i < selectedPaths.length; i++) {
511             Object obj = ((DefaultMutableTreeNode) selectedPaths[i].getLastPathComponent()).getUserObject();
512             selectedEntries += obj.toString();
513
514             if (i < selectedPaths.length - 1) {
515                 selectedEntries += NEWLINE;
516             }
517         }
518
519         return selectedEntries;
520     }
521
522     private static void scrollAtBottom() {
523         SwingUtilities.invokeLater(new Runnable() {
524             public void run() {
525                 scrollPane.getHorizontalScrollBar().setValue(0);
526                 scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
527             }
528         });
529
530     }
531
532     /**
533      * Inner class to render the session nodes in green
534      */
535     static class SessionString {
536
537         String s;
538
539         SessionString(String s) {
540             this.s = s;
541         }
542
543         public String toString() {
544             return s;
545         }
546     }
547
548     @SuppressWarnings(value = { "serial" })
549     static class HistoryTree extends JTree {
550
551         private boolean first = true;
552         private Color defaultColor;
553         private Color sessionColor = new Color(1, 168, 1);
554         boolean mustFire = true;
555
556         HistoryTree(TreeModel model) {
557             super(model);
558             setRowHeight(16);
559             setLargeModel(true);
560
561             setCellRenderer(new DefaultTreeCellRenderer() {
562                 {
563                     defaultColor = getTextNonSelectionColor();
564                 }
565
566                 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
567                     super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
568                     if (((DefaultMutableTreeNode) value).getUserObject() instanceof SessionString) {
569                         setTextNonSelectionColor(sessionColor);
570                     } else {
571                         setTextNonSelectionColor(defaultColor);
572                     }
573
574                     return this;
575                 }
576             });
577         }
578
579         public void fireTreeExpanded(TreePath path) {
580             if (mustFire) {
581                 super.fireTreeExpanded(path);
582             }
583         }
584
585         public void fireTreeWillExpand(TreePath path) throws ExpandVetoException {
586             if (mustFire) {
587                 super.fireTreeWillExpand(path);
588             }
589         }
590
591         public void paint(final Graphics g) {
592             if (first) {
593                 g.setFont(getFont());
594                 int height = g.getFontMetrics().getHeight();
595                 setRowHeight(height);
596                 setLargeModel(true);
597                 first = false;
598                 scrollPane.getVerticalScrollBar().setUnitIncrement(height);
599                 scrollAtBottom();
600             }
601             try {
602                 super.paint(g);
603             } catch (Exception e) {
604                 SwingUtilities.invokeLater(new Runnable() {
605                     public void run() {
606                         paint(g);
607                     }
608                 });
609             }
610         }
611     }
612 }