permute(): now supports rationals. UTests + help page overhauled
[scilab.git] / scilab / modules / xcos / src / java / org / scilab / modules / xcos / palette / view / PaletteManagerPanel.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - DIGITEO - Clement DAVID
4  * Copyright (C) 2011-2015 - Scilab Enterprises - Clement DAVID
5  * Copyright (C) 2015 - Marcos CARDINOT
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.xcos.palette.view;
19
20 import java.awt.Color;
21 import java.awt.event.ActionEvent;
22 import java.awt.event.ActionListener;
23 import java.awt.Component;
24 import java.awt.Dimension;
25 import java.awt.Toolkit;
26 import java.awt.event.MouseWheelEvent;
27 import java.awt.event.MouseWheelListener;
28 import java.util.ArrayList;
29 import java.util.List;
30
31 import javax.swing.DropMode;
32 import javax.swing.JPanel;
33 import javax.swing.JScrollBar;
34 import javax.swing.JScrollPane;
35 import javax.swing.JSplitPane;
36 import javax.swing.JTree;
37 import javax.swing.Timer;
38 import javax.swing.tree.TreeNode;
39 import javax.swing.tree.TreePath;
40 import javax.swing.tree.TreeSelectionModel;
41
42 import org.scilab.modules.xcos.JavaController;
43 import org.scilab.modules.xcos.Kind;
44 import org.scilab.modules.xcos.graph.PaletteDiagram;
45 import org.scilab.modules.xcos.palette.PaletteCtrl;
46 import org.scilab.modules.xcos.palette.PaletteBlockCtrl;
47 import org.scilab.modules.xcos.palette.PaletteManager;
48 import org.scilab.modules.xcos.palette.actions.NavigationAction;
49 import org.scilab.modules.xcos.palette.actions.ZoomAction;
50 import org.scilab.modules.xcos.palette.listener.PaletteManagerMouseListener;
51 import org.scilab.modules.xcos.palette.listener.PaletteManagerTreeSelectionListener;
52 import org.scilab.modules.xcos.palette.listener.PaletteTreeTransferHandler;
53 import org.scilab.modules.xcos.palette.model.Category;
54 import org.scilab.modules.xcos.palette.model.Custom;
55 import org.scilab.modules.xcos.palette.model.PaletteBlock;
56 import org.scilab.modules.xcos.palette.model.PaletteNode;
57 import org.scilab.modules.xcos.palette.model.PreLoaded;
58 import org.scilab.modules.xcos.utils.Stack;
59 import org.scilab.modules.xcos.utils.XcosConstants;
60 import org.scilab.modules.xcos.utils.XcosConstants.PaletteBlockSize;
61
62 /**
63  * The content pane for the block view
64  */
65 @SuppressWarnings(value = { "serial" })
66 public class PaletteManagerPanel extends JSplitPane {
67
68     /** Name of tree node for recently used blocks **/
69     public static final String RECENTLY_USED_BLOCKS = "Recently Used Blocks";
70
71     private static final int NUMBER_OF_DUMMY_BLOCKS = 10; // 10 blocks loaded,
72     private static final int LOAD_DUMMY_BLOCK_DELAY = 200; // one each 200 milliseconds
73
74     private static final class LoadBlock implements ActionListener {
75         private final PaletteManager controller;
76         private int loadedBlocks;
77
78         private Stack<TreeNode> nodeStack;
79         private Stack<PaletteBlock> blocksStack;
80
81         LoadBlock(final PaletteManager controller, final int loadedBlocks) {
82             this.controller = controller;
83             this.loadedBlocks = loadedBlocks;
84
85             nodeStack = new Stack<>();
86             nodeStack.push(controller.getRoot());
87             blocksStack = new Stack<>();
88         }
89
90         @Override
91         public void actionPerformed(ActionEvent e) {
92
93             // load only a limited set of blocks
94             if (loadedBlocks <= 0) {
95                 Timer timer = (Timer) e.getSource();
96                 timer.stop();
97             }
98
99             // consume a block
100             if (blocksStack.size() != 0) {
101                 loadTransferable(blocksStack.pop());
102                 return;
103             }
104
105             // no block is available, look for a Preloaded palette
106             while (nodeStack.size() != 0) {
107                 TreeNode o = nodeStack.pop();
108
109                 if (o instanceof PreLoaded) {
110                     // found it a palette, create a dummy transferable from the first block and store the others for later use.
111                     blocksStack.addAll(((PreLoaded) o).getBlock());
112
113                     if (blocksStack.size() != 0) {
114                         loadTransferable(blocksStack.pop());
115                         return;
116                     }
117                 } else {
118                     for (int i = 0; i < o.getChildCount(); i++) {
119                         nodeStack.push(o.getChildAt(i));
120                     }
121                 }
122             }
123         }
124
125         private void loadTransferable(final PaletteBlock blk) {
126             new PaletteBlockCtrl(new PaletteCtrl(), blk).getPaletteCtrl().getTransferable();
127             loadedBlocks -= 1;
128         }
129     }
130
131     private static XcosConstants.PaletteBlockSize currentSize;
132     private final PaletteManager controller;
133     private CustomMouseWheelListener mouseWheelListener;
134     private JTree tree;
135     private PaletteDiagram diagramInstance;
136     private List<TreePath> historyNext;
137     private List<TreePath> historyPrev;
138     private TreePath currentPath;
139
140     /**
141      * Default constructor
142      *
143      * @param controller
144      *            the {@link PaletteManager} instance
145      */
146     public PaletteManagerPanel(PaletteManager controller) {
147         super(JSplitPane.HORIZONTAL_SPLIT);
148         this.controller = controller;
149         this.mouseWheelListener = new CustomMouseWheelListener();
150         this.historyNext = new ArrayList<TreePath>();
151         this.historyPrev = new ArrayList<TreePath>();
152         this.currentPath = null;
153         currentSize = XcosConstants.PaletteBlockSize.NORMAL;
154         fillUpContentPane();
155     }
156
157     /**
158      * Fill up the content pane
159      */
160     private void fillUpContentPane() {
161         // Default instances
162         JScrollPane panel = new JScrollPane();
163         panel.setBackground(Color.WHITE);
164         setUpScrollBar(panel, currentSize);
165
166         // Set default left component
167         JPanel rootPalette = new JPanel();
168
169         TreeNode root = controller.getRoot();
170         tree = new JTree(new PaletteTreeModel(root));
171         JScrollPane treeScrollPane = new JScrollPane(tree);
172
173         // Setup tree
174         tree.getSelectionModel().setSelectionMode(
175             TreeSelectionModel.SINGLE_TREE_SELECTION);
176         tree.addMouseListener(new PaletteManagerMouseListener());
177         tree.addTreeSelectionListener(new PaletteManagerTreeSelectionListener(
178                                           this, panel));
179
180         tree.setEditable(false);
181         tree.setDragEnabled(true);
182         tree.setExpandsSelectedPaths(true);
183         tree.setDropMode(DropMode.INSERT);
184         tree.setTransferHandler(new PaletteTreeTransferHandler());
185
186         setLeftComponent(treeScrollPane);
187         panel.setViewportView(rootPalette);
188         setRightComponent(panel);
189     }
190
191     /**
192      * Get the current PaletteBlockSize
193      * @return current PaletteBlockSize
194      */
195     public static PaletteBlockSize getCurrentSize() {
196         return currentSize;
197     }
198
199     /**
200      * Adds a PaletteBlock to the list of recently used blocks.
201      * @param block PaletteBlock
202      */
203     public void addRecentltyUsedBlock(PaletteBlock block) {
204         PaletteNode currentNode = (PaletteNode) tree.getLastSelectedPathComponent();
205         if (currentNode != null && currentNode.getName().equals(RECENTLY_USED_BLOCKS)) {
206             return;
207         }
208
209         final Category root = PaletteManager.getInstance().getRoot();
210         List<PaletteNode> nodes = root.getNode();
211
212         PreLoaded p = null;
213         for (PaletteNode n : nodes) {
214             if (n.getName().equals(RECENTLY_USED_BLOCKS) && n instanceof PreLoaded) {
215                 p = (PreLoaded) n;
216                 break;
217             }
218         }
219
220         if (p == null) {
221             p = new PreLoaded();
222             p.setName(RECENTLY_USED_BLOCKS);
223             p.setEnable(true);
224             root.getNode().add(p);
225             p.setParent(root);
226             PaletteNode.refreshView(root, currentNode);
227         }
228
229         List<PaletteBlock> blocks = p.getBlock();
230         for (PaletteBlock b : blocks) {
231             if (b.getName().equals(block.getName())) {
232                 return;
233             }
234         }
235
236         if (blocks.size() >= XcosConstants.MAX_RECENTLY_USED_BLOCKS) {
237             blocks.remove(XcosConstants.MAX_RECENTLY_USED_BLOCKS - 1);
238         }
239         blocks.add(0, block);
240     }
241
242     /**
243      * Zoom
244      * @param newSize new paletteblocksize enum
245      */
246     // CSOFF: CyclomaticComplexity
247     private void zoom(PaletteBlockSize newSize) {
248         if (newSize == null || newSize == currentSize) {
249             return;
250         }
251
252         currentSize = newSize;
253
254         try {
255             JScrollPane jspR = (JScrollPane) this.getRightComponent();
256             final Dimension dimension = jspR.getPreferredSize();
257             setUpScrollBar(jspR, newSize);
258
259             // check what's being displayed on the right panel
260             PaletteNode node = (PaletteNode) tree.getLastSelectedPathComponent();
261             if (node instanceof PreLoaded || node == null) {
262                 Component c = jspR.getViewport().getComponent(0);
263                 String cName = c.getName();
264                 PaletteView pview;
265                 if (cName.equals("PaletteView")) {
266                     pview = (PaletteView) c;
267                 } else if (cName.equals("PaletteSearchView")) {
268                     PaletteSearchView sview = (PaletteSearchView) c;
269                     pview = (PaletteView) sview.getComponent(1);
270                 } else {
271                     return;
272                 }
273
274                 Component[] blockViews = pview.getComponents();
275                 for (Component component : blockViews) {
276                     PaletteBlockView bview = (PaletteBlockView) component;
277                     bview.initComponents();
278                 }
279                 pview.revalidate();
280             } else if (node instanceof Custom) {
281                 jspR = openDiagramAsPal(node);
282                 jspR.setPreferredSize(dimension);
283                 this.setRightComponent(jspR);
284             } else {
285                 return;
286             }
287
288             // update the status of both zoom buttons
289             if (newSize.next() == null) {
290                 // is it the last zoom-in level?
291                 ZoomAction.setEnabledZoomIn(false);
292                 ZoomAction.setEnabledZoomOut(true);
293             } else if (newSize.previous() == null) {
294                 // is it the last zoom-out level?
295                 ZoomAction.setEnabledZoomIn(true);
296                 ZoomAction.setEnabledZoomOut(false);
297             } else {
298                 ZoomAction.setEnabledZoomIn(true);
299                 ZoomAction.setEnabledZoomOut(true);
300             }
301
302             jspR.revalidate();
303         } catch (NullPointerException e) {
304             e.printStackTrace();
305         }
306     }
307
308     /** zoom in **/
309     public void zoomIn() {
310         zoom(currentSize.next());
311     }
312
313     /** zoom out **/
314     public void zoomOut() {
315         zoom(currentSize.previous());
316     }
317
318     /**
319      * Open a diagram as a palette.
320      * @param node PaletteNode
321      * @return a JScrollPane with the diagram
322      */
323     public JScrollPane openDiagramAsPal(PaletteNode node) {
324         String path = ((Custom) node).getPath().getEvaluatedPath();
325         JavaController ctroller = new JavaController();
326         this.diagramInstance = new PaletteDiagram(ctroller.createObject(Kind.DIAGRAM));
327         this.diagramInstance.openDiagramAsPal(path);
328         return this.diagramInstance.getAsComponent();
329     }
330
331     /**
332      * Setup the ScrollPane component
333      * @param jsp The component
334      * @param pbs PaletteBlockSize
335      */
336     private void setUpScrollBar(JScrollPane jsp, PaletteBlockSize pbs) {
337         // vertical
338         jsp.getVerticalScrollBar().setBlockIncrement(
339             pbs.getBlockDimension().height
340             + XcosConstants.PALETTE_VMARGIN);
341         jsp.getVerticalScrollBar().setUnitIncrement(
342             pbs.getBlockDimension().height
343             + XcosConstants.PALETTE_VMARGIN);
344         // horizontal
345         jsp.getHorizontalScrollBar().setBlockIncrement(
346             pbs.getBlockDimension().width
347             + XcosConstants.PALETTE_HMARGIN);
348         jsp.getHorizontalScrollBar().setUnitIncrement(
349             pbs.getBlockDimension().width
350             + XcosConstants.PALETTE_HMARGIN);
351
352         mouseWheelListener.setVerticalScrollBar(jsp.getVerticalScrollBar());
353
354         // removes the mouse wheel listeners
355         MouseWheelListener[] mwls = jsp.getMouseWheelListeners();
356         for (MouseWheelListener mwl : mwls) {
357             jsp.removeMouseWheelListener(mwl);
358         }
359         // adds the CustomMouseWheelListener
360         jsp.addMouseWheelListener(mouseWheelListener);
361     }
362
363     /**
364      * Setup the default layout
365      */
366     public void performStartUpLayout() {
367         /* Tree layout */
368         final Object root = tree.getModel().getRoot();
369         final Object firstChild = tree.getModel().getChild(root, 0);
370         final Object secondChild = tree.getModel().getChild(firstChild, 0);
371         tree.setSelectionPath(new TreePath(new Object[] {root, firstChild, secondChild}));
372
373         tree.setRootVisible(false);
374         tree.setScrollsOnExpand(true);
375
376         /* Global layout */
377         setContinuousLayout(true);
378
379         // Delay-load some blocks to pre-load jars used on the rendering.
380         // A timer is used to avoid busying EDT and add a small delay between blocks
381         Timer timer = new Timer(LOAD_DUMMY_BLOCK_DELAY, new LoadBlock(controller, NUMBER_OF_DUMMY_BLOCKS));
382         timer.start();
383     }
384
385     /**
386      * Updates the history.
387      */
388     public void updateHistory() {
389         TreePath lastPath = currentPath;
390         currentPath = tree.getSelectionPath();
391
392         if (lastPath != null && !currentPath.equals(lastPath)) {
393
394             if (historyPrev.isEmpty()) {
395                 historyPrev.add(lastPath);
396             } else {
397                 int prevPathIdx = historyPrev.size() - 1;
398                 // going backwards?
399                 if (currentPath.equals(historyPrev.get(prevPathIdx))) {
400                     historyNext.add(lastPath);
401                     historyPrev.remove(prevPathIdx);
402                     updateHistorySettings();
403                     return;
404                 } else {
405                     // it seems to be a new path, just store it!
406                     // Soon we'll check if we are just going forward
407                     // or if it's really a new path!
408                     historyPrev.add(lastPath);
409                 }
410             }
411
412             if (!historyNext.isEmpty()) {
413                 int nextPathIdx = historyNext.size() - 1;
414                 // going forward?
415                 if (currentPath.equals(historyNext.get(nextPathIdx))) {
416                     historyNext.remove(nextPathIdx);
417                 } else {
418                     // it is really a new path!
419                     // make sure it's on the top of the history!
420                     historyNext.clear();
421                 }
422             }
423             updateHistorySettings();
424         }
425     }
426
427     /**
428      * Update the status of the buttons and the history length.
429      */
430     private void updateHistorySettings() {
431         if (historyNext.size() + historyPrev.size() > XcosConstants.HISTORY_LENGTH) {
432             historyPrev.remove(0);
433         }
434         NavigationAction.setEnabledNext(historyNext.size() > 0);
435         NavigationAction.setEnabledPrev(historyPrev.size() > 0);
436     }
437
438     /**
439      * Go to the next path (history).
440      */
441     public void goNext() {
442         try {
443             tree.setSelectionPath(historyNext.get(historyNext.size() - 1));
444         } catch (ArrayIndexOutOfBoundsException err) {
445             NavigationAction.setEnabledNext(false);
446         }
447     }
448
449     /**
450      * Go to the previous path (history).
451      */
452     public void goPrevious() {
453         try {
454             tree.setSelectionPath(historyPrev.get(historyPrev.size() - 1));
455         } catch (ArrayIndexOutOfBoundsException err) {
456             NavigationAction.setEnabledPrev(false);
457         }
458     }
459
460     /**
461      * Implement custom mouse handling for the zoom
462      */
463     private static final class CustomMouseWheelListener implements MouseWheelListener {
464         private static final int ACCELERATOR_KEY = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
465         private JScrollBar verticalScrollBar;
466
467         /**
468          * Default constructor
469          */
470         public CustomMouseWheelListener() {
471         }
472
473         /**
474          * Set the vertical scrollbar
475          * It's important to update the unit increment
476          * @param verticalScrollBar JScrollBar
477          */
478         public void setVerticalScrollBar(JScrollBar verticalScrollBar) {
479             this.verticalScrollBar = verticalScrollBar;
480         }
481
482         /**
483          * When the wheel is used
484          * @param e The parameters
485          * @see java.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event.MouseWheelEvent)
486          */
487         @Override
488         public void mouseWheelMoved(MouseWheelEvent e) {
489             if (verticalScrollBar == null) {
490                 // nothing to do!
491                 return;
492             }
493
494             if (e.getModifiers() == ACCELERATOR_KEY) {
495                 if (e.getWheelRotation() < 0) {
496                     PaletteManagerView.get().getPanel().zoomIn();
497                 } else if (e.getWheelRotation() > 0) {
498                     PaletteManagerView.get().getPanel().zoomOut();
499                 }
500             } else {
501                 int i = verticalScrollBar.getValue();
502                 if (e.getWheelRotation() < 0) {
503                     i -= verticalScrollBar.getUnitIncrement();
504                 } else {
505                     i += verticalScrollBar.getUnitIncrement();
506                 }
507                 verticalScrollBar.setValue(i);
508             }
509         }
510     }
511 }