Xcos: reduce the lag at the first DnD #2 67/18267/5
Clément DAVID [Mon, 20 Jun 2016 14:26:07 +0000 (16:26 +0200)]
The first DnD of a block is slow on Windows as Batik and Java2D is not
setup on JVM at first start. To fully initialize all classes, we have to
render some blocks to images and suppose that these blocks will cover all
functionnalities (SVG strikes, fonts, bitmap images, etc...).

Change-Id: Ida995f3e19d7355c6a43886cc182c37cd0476eb4

scilab/modules/xcos/src/java/org/scilab/modules/xcos/JavaController.java
scilab/modules/xcos/src/java/org/scilab/modules/xcos/Xcos.java
scilab/modules/xcos/src/java/org/scilab/modules/xcos/graph/model/XcosCell.java
scilab/modules/xcos/src/java/org/scilab/modules/xcos/palette/PaletteBlockCtrl.java
scilab/modules/xcos/src/java/org/scilab/modules/xcos/palette/view/PaletteManagerPanel.java
scilab/modules/xcos/src/java/org/scilab/modules/xcos/utils/Stack.java

index d9d64d2..9e7aec4 100644 (file)
@@ -38,5 +38,4 @@ public class JavaController extends Controller {
     JavaControllerJNI.unregister_view(View.getCPtr(view), view);
     remove_reference(view);
   }
-
 }
index 3e2ae66..a08c082 100644 (file)
@@ -74,9 +74,6 @@ import com.mxgraph.model.mxICell;
 import com.mxgraph.util.mxEvent;
 import com.mxgraph.util.mxEventObject;
 import com.mxgraph.view.mxStylesheet;
-import javax.swing.SwingWorker;
-import org.scilab.modules.graph.ScilabCanvas;
-import org.scilab.modules.xcos.graph.swing.GraphComponent;
 
 /**
  * Xcos entry point class
@@ -121,22 +118,6 @@ public final class Xcos {
         });
 
         XConfiguration.addXConfigurationListener(new XcosConfiguration());
-
-        /*
-         * Load some classes in the background to avoid any lag on the first drag'n drop.
-         *
-         * This will setup the whole rendering stack by dummy rendering a block' style
-         */
-        (new SwingWorker<Void, Void>() {
-            @Override
-            protected Void doInBackground() throws Exception {
-                Map<String, Object> style = Xcos.getInstance().getStyleSheet().getCellStyle("CLOCK_c", new HashMap<>());
-                ScilabCanvas canvas = new GraphComponent(null).createCanvas();
-                canvas.paintSvgForegroundImage(1, 1, canvas.getImageForStyle(style));
-                return null;
-            }
-
-        }).execute();
     }
 
     /*
index ac9149f..4860d7e 100644 (file)
@@ -29,6 +29,7 @@ import com.mxgraph.model.mxCell;
 import com.mxgraph.model.mxGeometry;
 import com.mxgraph.model.mxICell;
 import com.mxgraph.util.mxPoint;
+import java.rmi.server.UID;
 import java.util.regex.Pattern;
 
 /**
@@ -138,6 +139,10 @@ public class XcosCell extends mxCell {
     }
 
     public final void setId(JavaController controller, String id) {
+        if (id == null || id.isEmpty()) {
+            id = new UID().toString();
+        }
+
         super.setId(id);
         setMVCId(controller, id);
     }
index 7143703..7d3dab1 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
  * Copyright (C) 2009 - DIGITEO - Clement DAVID
- * Copyright (C) 2011-2015 - Scilab Enterprises - Clement DAVID
+ * Copyright (C) 2011-2016 - Scilab Enterprises - Clement DAVID
  *
  * Copyright (C) 2012 - 2016 - Scilab Enterprises
  *
@@ -23,8 +23,6 @@ import java.awt.dnd.DragGestureEvent;
 import java.awt.dnd.DragGestureListener;
 import java.awt.dnd.DragSource;
 import java.awt.dnd.InvalidDnDOperationException;
-import java.awt.event.MouseListener;
-import java.lang.ref.WeakReference;
 import java.util.logging.Logger;
 
 import org.scilab.modules.gui.messagebox.ScilabModalDialog;
@@ -35,7 +33,6 @@ import org.scilab.modules.xcos.Kind;
 import org.scilab.modules.xcos.block.BasicBlock;
 import org.scilab.modules.xcos.graph.XcosDiagram;
 import org.scilab.modules.xcos.graph.model.XcosCellFactory;
-import org.scilab.modules.xcos.io.scicos.ScicosFormatException;
 import org.scilab.modules.xcos.palette.listener.PaletteBlockMouseListener;
 import org.scilab.modules.xcos.palette.model.PaletteBlock;
 import org.scilab.modules.xcos.palette.view.PaletteBlockView;
@@ -62,8 +59,7 @@ public final class PaletteBlockCtrl {
         INTERNAL_GRAPH.installListeners();
     }
 
-    private static final double BLOCK_DEFAULT_POSITION = 10.0;
-    private static final MouseListener MOUSE_LISTENER = new PaletteBlockMouseListener();
+    private static final double BLOCK_DEFAULT_POSITION = 10.;
     private static final Logger LOG = Logger.getLogger(PaletteBlockCtrl.class.getName());
 
     private static final String UNABLE_TO_LOAD_BLOCK = Messages.gettext("Unable to load block from %s .");
@@ -74,8 +70,6 @@ public final class PaletteBlockCtrl {
     private final PaletteBlock model;
     private final PaletteBlockView view;
 
-    private transient WeakReference<Transferable> transferable = new WeakReference<Transferable>(null);
-
     /**
      * Default constructor
      *
@@ -93,8 +87,10 @@ public final class PaletteBlockCtrl {
      *            The view to setup
      */
     private void installListeners(PaletteBlockView view) {
-        view.addMouseListener(MOUSE_LISTENER);
-        installDnd();
+        view.addMouseListener(new PaletteBlockMouseListener());
+
+        DragSource dragSource = DragSource.getDefaultDragSource();
+        dragSource.createDefaultDragGestureRecognizer(this.getView(), DnDConstants.ACTION_MOVE, new PaletteDragGestureListener(getModel(), this));
     }
 
     /**
@@ -115,55 +111,44 @@ public final class PaletteBlockCtrl {
      * This function is the only access to get the block.
      *
      * @return the transferable object
-     * @throws ScicosFormatException
-     *             on decoding error
      */
-    public synchronized Transferable getTransferable() throws ScicosFormatException {
-        Transferable transfer = transferable.get();
-        if (transfer == null) {
-            BasicBlock block;
-            try {
-                block = XcosCellFactory.createBlock(model.getName());
-            } catch (ScilabInterpreterManagement.InterpreterException ex) {
-                LOG.finest(String.format(UNABLE_TO_LOAD_BLOCK, model.getName()));
-                getView().setEnabled(false);
-                throw new InvalidDnDOperationException();
-            }
-            getView().setEnabled(true);
+    public Transferable getTransferable() {
+        Transferable transfer;
 
-            /* Render it and export it */
-            block.getGeometry().setX(BLOCK_DEFAULT_POSITION);
-            block.getGeometry().setY(BLOCK_DEFAULT_POSITION);
+        BasicBlock block;
+        try {
+            block = XcosCellFactory.createBlock(model.getName());
+        } catch (ScilabInterpreterManagement.InterpreterException ex) {
+            LOG.finest(String.format(UNABLE_TO_LOAD_BLOCK, model.getName()));
+            getView().setEnabled(false);
+            throw new InvalidDnDOperationException();
+        }
+        getView().setEnabled(true);
 
-            INTERNAL_GRAPH.addCell(block);
-            INTERNAL_GRAPH.selectAll();
+        /* Render it and export it */
+        block.getGeometry().setX(BLOCK_DEFAULT_POSITION);
+        block.getGeometry().setY(BLOCK_DEFAULT_POSITION);
 
-            BlockPositioning.updateBlockView(INTERNAL_GRAPH, block);
+        INTERNAL_GRAPH.addCell(block);
+        INTERNAL_GRAPH.selectAll();
 
-            mxGraphTransferHandler handler = ((mxGraphTransferHandler) INTERNAL_GRAPH.getAsComponent().getTransferHandler());
-            Object[] cells = new Object[] {block};
-            transfer = new mxGraphTransferable(cells, INTERNAL_GRAPH.getPaintBounds(cells), handler.createTransferableImage(INTERNAL_GRAPH.getAsComponent(), cells));
-            transferable = new WeakReference<Transferable>(transfer);
+        BlockPositioning.updateBlockView(INTERNAL_GRAPH, block);
 
-            INTERNAL_GRAPH.removeCells();
-        }
+        mxGraphTransferHandler handler = ((mxGraphTransferHandler) INTERNAL_GRAPH.getAsComponent().getTransferHandler());
+        Object[] cells = new Object[] {block};
+        transfer = new mxGraphTransferable(cells, INTERNAL_GRAPH.getPaintBounds(cells), handler.createTransferableImage(INTERNAL_GRAPH.getAsComponent(), cells));
+
+        INTERNAL_GRAPH.removeCells();
         return transfer;
     }
 
     /**
-     * This function load the block and render it on the hidden diagram. This
-     * can be time-consuming and each block should be cached on the caller when
-     * possible.
+     * This function load the block and render it on the hidden diagram.
      *
      * @return a rendered block
      */
     public BasicBlock getBlock() {
-        try {
-            return (BasicBlock) ((mxGraphTransferable) getTransferable()).getCells()[0];
-        } catch (ScicosFormatException e) {
-            LOG.severe(e.toString());
-            return null;
-        }
+        return (BasicBlock) ((mxGraphTransferable) getTransferable()).getCells()[0];
     }
 
     /**
@@ -188,41 +173,40 @@ public final class PaletteBlockCtrl {
     }
 
     /**
-     * Install the Drag'n'Drop on this instance.
+     * Drag 'n drop implementation, allocate a block on demand.
      */
-    public void installDnd() {
-        // Install the handler for dragging nodes into a graph
-        DragGestureListener dragGestureListener = new DragGestureListener() {
-            @Override
-            public void dragGestureRecognized(DragGestureEvent e) {
-                if (PaletteManagerView.get() == null) {
-                    PaletteManagerView.restore(null);
-                }
-                final PaletteManagerView winView = PaletteManagerView.get();
-                final DragGestureEvent event = e;
-                final String msg = String.format(UNABLE_TO_LOAD_BLOCK, getModel().getName());
-
-                winView.setInfo(LOADING_THE_BLOCK);
-                try {
-                    Transferable transfer = getTransferable();
-
-                    if (transfer != null) {
-                        event.startDrag(null, null, new Point(), transfer, null);
-                    } else {
-                        throw new InvalidDnDOperationException();
-                    }
-                } catch (InvalidDnDOperationException exception) {
-                    ScilabModalDialog.show(winView, msg, XcosMessages.XCOS_ERROR, IconType.ERROR_ICON);
-                } catch (ScicosFormatException ex) {
-                    ScilabModalDialog.show(winView, ex.getMessage(), XcosMessages.XCOS_ERROR, IconType.ERROR_ICON);
-                } finally {
-                    winView.setInfo(XcosMessages.EMPTY_INFO);
-                }
+    private static final class PaletteDragGestureListener implements DragGestureListener {
+
+        private final PaletteBlock block;
+        private final PaletteBlockCtrl controller;
+
+        public PaletteDragGestureListener(final PaletteBlock block, final PaletteBlockCtrl controller) {
+            this.block = block;
+            this.controller = controller;
+        }
+
+        @Override
+        public void dragGestureRecognized(DragGestureEvent event) {
+            if (PaletteManagerView.get() == null) {
+                PaletteManagerView.restore(null);
             }
+            final PaletteManagerView winView = PaletteManagerView.get();
 
-        };
+            winView.setInfo(LOADING_THE_BLOCK);
+            try {
+                Transferable transfer = controller.getTransferable();
+                if (transfer != null) {
+                    event.startDrag(null, null, new Point(), transfer, null);
+                } else {
+                    throw new InvalidDnDOperationException();
+                }
+            } catch (InvalidDnDOperationException exception) {
+                final String msg = String.format(UNABLE_TO_LOAD_BLOCK, block.getName());
+                ScilabModalDialog.show(winView, msg, XcosMessages.XCOS_ERROR, IconType.ERROR_ICON);
+            } finally {
+                winView.setInfo(XcosMessages.EMPTY_INFO);
+            }
+        }
 
-        DragSource dragSource = DragSource.getDefaultDragSource();
-        dragSource.createDefaultDragGestureRecognizer(this.getView(), DnDConstants.ACTION_COPY, dragGestureListener);
     }
 }
index 605940c..5879d1a 100644 (file)
 package org.scilab.modules.xcos.palette.view;
 
 import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
 
 import javax.swing.DropMode;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JSplitPane;
 import javax.swing.JTree;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
 import javax.swing.tree.TreeNode;
 import javax.swing.tree.TreePath;
 import javax.swing.tree.TreeSelectionModel;
+import org.scilab.modules.xcos.palette.PaletteBlockCtrl;
 
 import org.scilab.modules.xcos.palette.PaletteManager;
 import org.scilab.modules.xcos.palette.listener.PaletteManagerMouseListener;
 import org.scilab.modules.xcos.palette.listener.PaletteManagerTreeSelectionListener;
 import org.scilab.modules.xcos.palette.listener.PaletteManagerTreeWillExpandListener;
 import org.scilab.modules.xcos.palette.listener.PaletteTreeTransferHandler;
+import org.scilab.modules.xcos.palette.model.PaletteBlock;
+import org.scilab.modules.xcos.palette.model.PreLoaded;
+import org.scilab.modules.xcos.utils.Stack;
 import org.scilab.modules.xcos.utils.XcosConstants;
 
 /**
@@ -39,8 +48,67 @@ import org.scilab.modules.xcos.utils.XcosConstants;
  */
 @SuppressWarnings(value = { "serial" })
 public class PaletteManagerPanel extends JSplitPane {
+    private static final int NUMBER_OF_DUMMY_BLOCKS = 10; // 10 blocks loaded,
+    private static final int LOAD_DUMMY_BLOCK_DELAY = 200; // one each 200 milliseconds
+
+    private static final class LoadBlock implements ActionListener {
+        private final PaletteManager controller;
+        private int loadedBlocks;
+
+        private Stack<TreeNode> nodeStack;
+        private Stack<PaletteBlock> blocksStack;
+
+        LoadBlock(final PaletteManager controller, final int loadedBlocks) {
+            this.controller = controller;
+            this.loadedBlocks = loadedBlocks;
+
+            nodeStack = new Stack<>();
+            nodeStack.push(controller.getRoot());
+            blocksStack = new Stack<>();
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+
+            // load only a limited set of blocks
+            if (loadedBlocks <= 0) {
+                Timer timer = (Timer) e.getSource();
+                timer.stop();
+            }
+
+            // consume a block
+            if (blocksStack.size() != 0) {
+                loadTransferable(blocksStack.pop());
+                return;
+            }
+
+            // no block is available, look for a Preloaded palette
+            while (nodeStack.size() != 0) {
+                TreeNode o = nodeStack.pop();
+
+                if (o instanceof PreLoaded) {
+                    // found it a palette, create a dummy transferable from the first block and store the others for later use.
+                    blocksStack.addAll(((PreLoaded) o).getBlock());
+
+                    if (blocksStack.size() != 0) {
+                        loadTransferable(blocksStack.pop());
+                        return;
+                    }
+                } else {
+                    for (int i = 0; i < o.getChildCount(); i++) {
+                        nodeStack.push(o.getChildAt(i));
+                    }
+                }
+            }
+        }
+
+        private void loadTransferable(final PaletteBlock blk) {
+            new PaletteBlockCtrl(blk).getTransferable();
+            loadedBlocks -= 1;
+        }
+    }
 
-    private PaletteManager controller;
+    private final PaletteManager controller;
 
     /**
      * Default constructor
@@ -129,5 +197,10 @@ public class PaletteManagerPanel extends JSplitPane {
 
         /* Global layout */
         setContinuousLayout(true);
+
+        // Delay-load some blocks to pre-load jars used on the rendering.
+        // A timer is used to avoid busying EDT and add a small delay between blocks
+        Timer timer = new Timer(LOAD_DUMMY_BLOCK_DELAY, new LoadBlock(controller, NUMBER_OF_DUMMY_BLOCKS));
+        timer.start();
     }
 }
index 805d75f..067dda5 100644 (file)
@@ -16,6 +16,7 @@
 package org.scilab.modules.xcos.utils;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Vector;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
@@ -30,14 +31,18 @@ import java.util.stream.Stream;
 public class Stack<E> {
     private ArrayList<E> stack = new ArrayList<>();
 
-    public void pop() {
-        stack.remove(stack.size() - 1);
+    public E pop() {
+        return stack.remove(stack.size() - 1);
     }
 
     public void push(E e) {
         stack.add(e);
     }
 
+    public void addAll(Collection<E> c) {
+        stack.addAll(c);
+    }
+
     public E peek() {
         return peek(0);
     }