Bug fix #11530 & #11363: Raise (help, scinotes...) windows when iconified
[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         } else {
163         }
164     }
165
166     /**
167      * Create a new Command History tab
168      * @return the corresponding tab
169      */
170     public static SwingScilabDockablePanel createCommandHistoryTab() {
171         browserTab = new CommandHistory();
172         WindowsConfigurationManager.restorationFinished(browserTab);
173
174         return browserTab;
175     }
176
177     /**
178      * {@inheritDoc}
179      */
180     public void addInfoBar(TextBox infoBarToAdd) {
181         setInfoBar(infoBarToAdd);
182     }
183
184     /**
185      * {@inheritDoc}
186      */
187     public void addMenuBar(MenuBar menuBarToAdd) {
188         setMenuBar(menuBarToAdd);
189     }
190
191     /**
192      * {@inheritDoc}
193      */
194     public void addToolBar(ToolBar toolBarToAdd) {
195         setToolBar(toolBarToAdd);
196     }
197
198     /**
199      * {@inheritDoc}
200      */
201     public SwingScilabWindow getParentWindow() {
202         return SwingScilabWindow.allScilabWindows.get(getParentWindowId());
203     }
204
205     /**
206      * {@inheritDoc}
207      */
208     public SimpleTab getAsSimpleTab() {
209         return this;
210     }
211
212     /**
213      * Update the browser once an history file has been loaded
214      */
215     public static void loadFromFile() {
216         final String historyLines[] = HistoryManagement.getAllLinesOfScilabHistory();
217
218         SwingUtilities.invokeLater(new Runnable() {
219             @Override
220             public void run() {
221                 reset();
222                 int nbEntries = historyLines.length;
223                 for (int entryIndex = 0; entryIndex < nbEntries; entryIndex++) {
224                     /* Do not expand at each insertion for performances reasons */
225                     appendLineAndExpand(historyLines[entryIndex], false);
226                 }
227             }
228         });
229
230         /* Expand all sessions tree */
231         expandAll();
232     }
233
234     public static void expandAll() {
235         if (isHistoryVisible()) {
236             // put the expansion in an invokeLater to avoid some kind of freeze with huge history
237             SwingUtilities.invokeLater(new Runnable() {
238                 public void run() {
239                     scilabHistoryTree.setVisible(true);
240                     if (!modelLoaded) {
241                         scilabHistoryTreeModel.nodeStructureChanged((TreeNode) scilabHistoryTreeModel.getRoot());
242                         modelLoaded = true;
243                     }
244
245                     final Object root = scilabHistoryTreeModel.getRoot();
246                     final TreePath pathRoot = new TreePath(root);
247                     final int N = scilabHistoryTreeModel.getChildCount(root);
248                     scilabHistoryTree.mustFire = false;
249                     for (int i = 0; i < N; i++) {
250                         Object o = scilabHistoryTreeModel.getChild(root, i);
251                         if (!scilabHistoryTreeModel.isLeaf(o)) {
252                             scilabHistoryTree.expandPath(pathRoot.pathByAddingChild(o));
253                         }
254                     }
255                     scilabHistoryTree.mustFire = true;
256                     scilabHistoryTree.fireTreeExpanded(pathRoot);
257
258                     scrollAtBottom();
259                 }
260             });
261         }
262     }
263
264     /**
265      * Add a new line to the History Browser
266      * @param lineToAppend the line to append
267      */
268     public static void appendLine(String lineToAppend) {
269         synchronized (linesToAppend) {
270             linesToAppend.add(lineToAppend);
271             linesToAppendTimer.start();
272         }
273     }
274
275     public static void appendLinesOnEDT() {
276         synchronized (linesToAppend) {
277             for (String lineToAppend : linesToAppend) {
278                 appendLineAndExpand(lineToAppend, true);
279             }
280             linesToAppend.clear();
281         }
282     }
283
284     /**
285      * check if line is a begin session
286      * @param line to check
287      * @retour true or false
288      */
289     private static boolean isBeginSessionLine(String lineToAppend) {
290         if (lineToAppend.startsWith(SESSION_BEGINNING) && lineToAppend.endsWith(SESSION_ENDING)) {
291             return true;
292         }
293         return false;
294     }
295
296     /**
297      * Add a new line to the History Browser
298      * @param lineToAppend the line to append
299      * @param expand do we need to expand all session nodes?
300      */
301     public static void appendLineAndExpand(String lineToAppend, boolean expand) {
302         if (isBeginSessionLine(lineToAppend)) {
303             // Create a new session node
304             currentSessionNode = new DefaultMutableTreeNode(new SessionString(lineToAppend));
305             scilabHistoryTreeModel.insertNodeInto(currentSessionNode, scilabHistoryRootNode, scilabHistoryRootNode.getChildCount());
306             if (expand && isHistoryVisible()) {
307                 scilabHistoryTree.expandRow(scilabHistoryTree.getRowCount() - 1);
308                 scilabHistoryTree.scrollPathToVisible(new TreePath(currentSessionNode.getPath()));
309             }
310         } else {
311             boolean mustScroll = false;
312             if (expand && isHistoryVisible()) {
313                 JScrollBar vb = scrollPane.getVerticalScrollBar();
314                 if (vb != null) {
315                     BoundedRangeModel model = vb.getModel();
316                     // mustScroll is true if the knob is at the bottom of the scollbar.
317                     mustScroll = model.getValue() == model.getMaximum() - model.getExtent();
318                 }
319             }
320             DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(lineToAppend);
321             scilabHistoryTreeModel.insertNodeInto(childNode, currentSessionNode, currentSessionNode.getChildCount());
322             if (expand && isHistoryVisible()) {
323                 scilabHistoryTree.expandRow(scilabHistoryTree.getRowCount() - 1);
324                 if (mustScroll) {
325                     scilabHistoryTree.scrollPathToVisible(new TreePath(childNode.getPath()));
326                 }
327             }
328         }
329     }
330
331     /**
332      * Reset the History Browser (after a clear)
333      */
334     public static void reset() {
335         scilabHistoryRootNode.removeAllChildren();
336         scilabHistoryTreeModel.reload();
337         currentSessionNode = null;
338     }
339
340     /**
341      * Remove an entry from History
342      * @param lineNumber the number of the line
343      */
344     public static void deleteLine(int lineNumber) {
345         int numberOfSessions = scilabHistoryRootNode.getChildCount();
346         int sessionIndex = 0;
347         int numberOfLines = 0;
348
349         while (sessionIndex < numberOfSessions) {
350             if (numberOfLines == lineNumber) {
351                 // Do we try to delete the last session node (which it is the current session used)
352                 if (sessionIndex == (numberOfSessions - 1)) {
353                     // Yes, we forbid it
354                     MessageBox errorMsg = ScilabMessageBox.createMessageBox();
355                     errorMsg.setTitle(CommandHistoryMessages.ERROR);
356                     errorMsg.setMessage(CommandHistoryMessages.CANNOT_DELETE_CURRENT_SESSION_NODE);
357                     errorMsg.setIcon("error");
358                     errorMsg.displayAndWait();
359                     return;
360                 }
361
362                 // Otherwise it is OK, delete session node
363                 TreeNode sessionNode = scilabHistoryRootNode.getChildAt(sessionIndex);
364                 scilabHistoryRootNode.remove(sessionIndex);
365                 scilabHistoryTreeModel.nodesWereRemoved(scilabHistoryRootNode, new int[] {sessionIndex},
366                                                         new Object[] {sessionNode});
367                 break;
368             }
369
370             DefaultMutableTreeNode sessionNode = (DefaultMutableTreeNode) scilabHistoryRootNode.getChildAt(sessionIndex);
371
372             // Did we reach the session containing the line to remove ?
373             if (numberOfLines + sessionNode.getChildCount() >= lineNumber) {
374                 // Yes, delete the child line
375                 int childIndex = lineNumber - numberOfLines - 1;
376                 TreeNode childNode = sessionNode.getChildAt(childIndex);
377                 sessionNode.remove(childIndex);
378                 scilabHistoryTreeModel.nodesWereRemoved(sessionNode, new int[] {childIndex},
379                                                         new Object[] {childNode} );
380                 break;
381             } else {
382                 /* No, jump to next session */
383                 numberOfLines += sessionNode.getChildCount() + 1;
384                 sessionIndex++;
385             }
386         }
387     }
388
389     /**
390      * Close the tab
391      */
392     public static void closeHistory() {
393         browserTab = null;
394     }
395
396     /**
397      * @return the browserTab
398      */
399     public static SwingScilabDockablePanel getBrowserTab() {
400         return browserTab;
401     }
402
403     /**
404      * Manage History Browser visibility
405      */
406     public static void setVisible() {
407         if (browserTab == null) {
408             boolean success = WindowsConfigurationManager.restoreUUID(COMMANDHISTORYUUID);
409             if (!success) {
410                 CommandHistoryTabFactory.getInstance().getTab(COMMANDHISTORYUUID);
411                 SwingScilabWindow window = SwingScilabWindow.createWindow(true);
412                 window.addTab(browserTab);
413                 window.setLocation(0, 0);
414                 window.setSize(500, 500);
415                 window.setVisible(true);
416             }
417         } else {
418             SwingScilabWindow window = (SwingScilabWindow) SwingUtilities.getAncestorOfClass(SwingScilabWindow.class, browserTab);
419             if(window != null) {
420                 int state = window.getExtendedState();
421                 if((state & SwingScilabWindow.ICONIFIED) == SwingScilabWindow.ICONIFIED) {
422                     window.setExtendedState(state - SwingScilabWindow.ICONIFIED);
423                 }
424             }
425         }
426         
427         browserTab.setVisible(true);
428         expandAll();
429     }
430
431     /**
432      * Launch the history browser
433      */
434     public static void launchHistoryBrowser() {
435         setVisible();
436     }
437
438     /**
439      * Get History Browser visibility
440      * @return visibility status
441      */
442     private static boolean isHistoryVisible() {
443         return browserTab != null && browserTab.isVisible();
444     }
445
446     /**
447      * Create History Browser MenuBar
448      * @return the menu bar
449      */
450     private static MenuBar createMenuBar() {
451         MenuBar menuBar = ScilabMenuBar.createMenuBar();
452
453         Menu fileMenu = ScilabMenu.createMenu();
454         fileMenu.setText(CommandHistoryMessages.FILE);
455         fileMenu.setMnemonic('F');
456
457         fileMenu.add(CloseAction.createMenuItem());
458
459         menuBar.add(fileMenu);
460
461         Menu editMenu = ScilabMenu.createMenu();
462         editMenu.setText(CommandHistoryMessages.EDIT);
463         editMenu.setMnemonic('E');
464
465         editMenu.add(CopyAction.createMenuItem());
466         editMenu.add(CutAction.createMenuItem());
467         editMenu.add(EvaluateAction.createMenuItem());
468         editMenu.add(EditInScinotesAction.createMenuItem());
469         editMenu.addSeparator();
470         editMenu.add(DeleteAction.createMenuItem());
471         editMenu.add(ClearAction.createMenuItem());
472
473         menuBar.add(editMenu);
474
475         Menu helpMenu = ScilabMenu.createMenu();
476         helpMenu.setText(CommandHistoryMessages.HELP);
477         helpMenu.setMnemonic('?');
478
479         helpMenu.add(HelpAction.createMenuItem());
480
481         menuBar.add(helpMenu);
482
483         return menuBar;
484     }
485
486     /**
487      * Create History Browser ToolBar
488      * @return the tool bar
489      */
490     private static ToolBar createToolBar() {
491         ToolBar toolBar = ScilabToolBar.createToolBar();
492         SwingScilabToolBar stb = (SwingScilabToolBar) toolBar.getAsSimpleToolBar();
493
494         stb.add(CopyAction.createPushButton());
495         stb.add(CutAction.createPushButton());
496         stb.add(DeleteAction.createPushButton());
497
498         stb.addSeparator();
499         stb.add(PrefsAction.createPushButton());
500         stb.addSeparator();
501
502         stb.add(HelpAction.createPushButton());
503
504         return toolBar;
505     }
506
507     /**
508      * Get the JTree
509      * @return the tree
510      */
511     public static JTree getTree() {
512         return scilabHistoryTree;
513     }
514
515     /**
516      * Get the JTree Model
517      * @return the tree model
518      */
519     public static DefaultTreeModel getTreeModel() {
520         return scilabHistoryTreeModel;
521     }
522
523     /**
524      * Get the selected commands and store them into an "executable" string
525      * @return the string
526      */
527     public static String getSelectedCommands() {
528         TreePath[] selectedPaths = CommandHistory.getTree().getSelectionPaths();
529
530         if (selectedPaths == null) {
531             return null;
532         }
533
534         String selectedEntries = new String();
535
536         for (int i = 0; i < selectedPaths.length; i++) {
537             Object obj = ((DefaultMutableTreeNode) selectedPaths[i].getLastPathComponent()).getUserObject();
538             selectedEntries += obj.toString();
539
540             if (i < selectedPaths.length - 1) {
541                 selectedEntries += NEWLINE;
542             }
543         }
544
545         return selectedEntries;
546     }
547
548     private static void scrollAtBottom() {
549         SwingUtilities.invokeLater(new Runnable() {
550             public void run() {
551                 scrollPane.getHorizontalScrollBar().setValue(0);
552                 scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
553             }
554         });
555
556     }
557
558     /**
559      * Inner class to render the session nodes in green
560      */
561     static class SessionString {
562
563         String s;
564
565         SessionString(String s) {
566             this.s = s;
567         }
568
569         public String toString() {
570             return s;
571         }
572     }
573
574     @SuppressWarnings(value = { "serial" })
575     static class HistoryTree extends JTree {
576
577         private boolean first = true;
578         private Color defaultColor;
579         private Color sessionColor = new Color(1, 168, 1);
580         boolean mustFire = true;
581
582         HistoryTree(TreeModel model) {
583             super(model);
584             setRowHeight(16);
585             setLargeModel(true);
586
587             setCellRenderer(new DefaultTreeCellRenderer() {
588                 {
589                     defaultColor = getTextNonSelectionColor();
590                 }
591
592                 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
593                     super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
594                     if (((DefaultMutableTreeNode) value).getUserObject() instanceof SessionString) {
595                         setTextNonSelectionColor(sessionColor);
596                     } else {
597                         setTextNonSelectionColor(defaultColor);
598                     }
599
600                     return this;
601                 }
602             });
603         }
604
605         public void fireTreeExpanded(TreePath path) {
606             if (mustFire) {
607                 super.fireTreeExpanded(path);
608             }
609         }
610
611         public void fireTreeWillExpand(TreePath path) throws ExpandVetoException {
612             if (mustFire) {
613                 super.fireTreeWillExpand(path);
614             }
615         }
616
617         public void paint(final Graphics g) {
618             if (first) {
619                 g.setFont(getFont());
620                 int height = g.getFontMetrics().getHeight();
621                 setRowHeight(height);
622                 setLargeModel(true);
623                 first = false;
624                 scrollPane.getVerticalScrollBar().setUnitIncrement(height);
625                 scrollAtBottom();
626             }
627             try {
628                 super.paint(g);
629             } catch (Exception e) {
630                 SwingUtilities.invokeLater(new Runnable() {
631                     public void run() {
632                         paint(g);
633                     }
634                 });
635             }
636         }
637     }
638 }