Index: DefaultJExpressionBuilder.java
===================================================================
--- DefaultJExpressionBuilder.java	(revision 43982)
+++ DefaultJExpressionBuilder.java	(revision 43983)
@@ -1,26 +1,60 @@
-
 package org.gvsig.expressionevaluator.swing.impl;
 
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
-import javax.swing.DefaultListModel;
+import java.util.Objects;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.ImageIcon;
 import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JTree;
 import javax.swing.SwingUtilities;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
 import javax.swing.event.TreeModelListener;
 import javax.swing.event.TreeSelectionEvent;
 import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultTreeCellRenderer;
 import javax.swing.tree.TreeModel;
 import javax.swing.tree.TreePath;
+import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.gvsig.expressionevaluator.Expression;
+import org.gvsig.expressionevaluator.ExpressionEvaluatorLocator;
+import org.gvsig.expressionevaluator.ExpressionEvaluatorManager;
 import org.gvsig.expressionevaluator.Function;
+import org.gvsig.expressionevaluator.SymbolTable;
+import org.gvsig.expressionevaluator.swing.Element;
+import org.gvsig.expressionevaluator.swing.Element.ConstantElement;
+import org.gvsig.expressionevaluator.swing.Element.FunctionElement;
+import org.gvsig.expressionevaluator.swing.Element.GroupElement;
+import org.gvsig.expressionevaluator.swing.Element.SimpleElement;
+import org.gvsig.expressionevaluator.swing.Element.VariableElement;
+import org.gvsig.expressionevaluator.swing.ExpressionBuilderConfig;
 import org.gvsig.expressionevaluator.swing.ExpressionEvaluatorSwingManager;
 import org.gvsig.expressionevaluator.swing.JExpressionBuilder;
+import org.gvsig.tools.ToolsLocator;
+import org.gvsig.tools.script.Script;
 import org.gvsig.tools.swing.api.ListElement;
 import org.gvsig.tools.swing.api.ToolsSwingLocator;
 import org.gvsig.tools.swing.api.ToolsSwingManager;
+import org.gvsig.tools.swing.api.pickercontroller.PickerController;
+import org.gvsig.tools.swing.api.script.ScriptSwingManager;
+import org.gvsig.tools.swing.icontheme.IconTheme;
 
 /**
  *
@@ -31,42 +65,54 @@
         implements JExpressionBuilder {
 
     private final ExpressionEvaluatorSwingManager manager;
-    private List<Element> elements;
     private GroupElement currentGroup;
+    private PickerController<List<Script>> scriptPicker;
+    private ScriptSwingManager.ScriptEditor scriptEditor;
     
+    private ExpressionBuilderConfig config;
+    
     private class ElementsTreeModel implements TreeModel {
 
         private final GroupElement root;
-        
+
         public ElementsTreeModel(GroupElement root) {
             this.root = root;
         }
-        
+
         @Override
         public Object getRoot() {
             return this.root;
         }
 
         private List<Element> getChilds(final GroupElement node) {
-            return node.getElements();
+            List<Element> x = new ArrayList<>();
+            for (Element element : node.getElements()) {
+                if (element instanceof GroupElement) {
+                    x.add(element);
+                }
+            }
+            return x; //node.getElements();
         }
-        
+
         @Override
         public Object getChild(Object parent, int index) {
-            List<Element> childs = getChilds((DefaultGroupElement)parent);
-            return childs.get(index);
+            List<Element> childs = getChilds((GroupElement) parent);
+            Element child = childs.get(index);
+            return child;
         }
 
         @Override
         public int getChildCount(Object parent) {
-            List<Element> childs = getChilds((DefaultGroupElement)parent);
+            List<Element> childs = getChilds((GroupElement) parent);
             return childs.size();
         }
 
         @Override
         public boolean isLeaf(Object node) {
-            boolean leaf = node instanceof SimpleElement;
-            return leaf;
+            if( node instanceof GroupElement ) {
+                return !((GroupElement) node).hasSubgroups();
+            }
+            return false;
         }
 
         @Override
@@ -75,7 +121,7 @@
 
         @Override
         public int getIndexOfChild(Object parent, Object child) {
-            List<Element> childs = getChilds((DefaultGroupElement)parent);
+            List<Element> childs = getChilds((GroupElement) parent);
             int n = childs.indexOf(child);
             return n;
         }
@@ -87,35 +133,69 @@
         @Override
         public void removeTreeModelListener(TreeModelListener l) {
         }
-                
+
     }
-    
-    
-    public DefaultJExpressionBuilder(ExpressionEvaluatorSwingManager manager) {
+
+    public DefaultJExpressionBuilder(ExpressionEvaluatorSwingManager manager, ExpressionBuilderConfig config) {
         this.manager = manager;
+        this.config = new DefaultExpressionBuilderConfig(manager);
         this.initComponents();
     }
+    
+    @Override
+    public ImageIcon loadImage(String imageName) {
+        String name = FilenameUtils.getBaseName(imageName);
+        IconTheme theme = ToolsSwingLocator.getIconThemeManager().getDefault();
+        if (theme.exists(name)) {
+            return theme.get(name);
+        }
+        URL url = this.getClass().getResource(name + ".png");
+        if (url == null) {
+            return null;
+        }
+        return new ImageIcon(url);
+    }
 
     private void initComponents() {
         ToolsSwingManager toolsSwingManager = ToolsSwingLocator.getToolsSwingManager();
+        final IconTheme theme = ToolsSwingLocator.getIconThemeManager().getCurrent();
         
         toolsSwingManager.setDefaultPopupMenu(this.txtExpression);
         toolsSwingManager.setDefaultPopupMenu(this.txtGroupElement);
         toolsSwingManager.setDefaultPopupMenu(this.txtSimpleElementFilter);
         toolsSwingManager.setDefaultPopupMenu(this.txtDescription);
-        toolsSwingManager.addClearButton(this.txtSimpleElementFilter);
+        toolsSwingManager.addClearButton(this.txtExpression);
+        toolsSwingManager.addClearButton(this.txtSimpleElementFilter, new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                txtSimpleElementFilter.setText("");
+                doFilter();
+            }
+        });
         
-        TreeModel model = new ElementsTreeModel(
-                this.manager.createGroup("Elements", this.getElements())
-        );
-        this.treeElements.setModel(model);
+        this.treeElements.setCellRenderer(new DefaultTreeCellRenderer() {
+            @Override
+            public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
+                JLabel label = (JLabel) super.getTreeCellRendererComponent(tree,value, selected, expanded, leaf, row, hasFocus);
+                if( value instanceof Element ) {
+                    Element element = (Element) value;
+                    label.setIcon(element.getIcon());
+                    label.setText(element.getName());
+                } else {
+                    label.setIcon(null);
+                    label.setText(Objects.toString(value, ""));
+                }
+                return label;
+            }
+        });
+        
         this.treeElements.addTreeSelectionListener(new TreeSelectionListener() {
             @Override
             public void valueChanged(TreeSelectionEvent e) {
                 TreePath path = e.getPath();
                 Element element = (Element) path.getLastPathComponent();
-                if( element instanceof GroupElement) {
-                    updateSimpleElementsList((GroupElement)element);
+                if (element instanceof GroupElement) {
+                    updateSimpleElementsList((GroupElement) element);
                 }
             }
         });
@@ -128,8 +208,10 @@
         this.btnSimpleElementInsert.addActionListener(new ActionListener() {
             @Override
             public void actionPerformed(ActionEvent e) {
-                SimpleElement element = (SimpleElement) lstSimpleElement.getSelectedValue();
-                doInsert(element);
+                ListElement<SimpleElement> element = (ListElement<SimpleElement>) lstSimpleElement.getSelectedValue();
+                if (element != null) {
+                    doInsert(element.getValue());
+                }
             }
         });
         this.btnSimpleElementFilter.addActionListener(new ActionListener() {
@@ -138,58 +220,204 @@
                 doFilter();
             }
         });
+        this.lstSimpleElement.setCellRenderer(new DefaultListCellRenderer() {
+            @Override
+            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+                JLabel label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+                Element element = null;
+                if( value instanceof ListElement ) {
+                    if( ((ListElement) value).getValue() instanceof Element ) {
+                        element = (Element) ((ListElement) value).getValue();
+                    }
+                }
+                if( element !=null ) {
+                    label.setIcon(element.getIcon());
+                    label.setText(element.getName());
+                } else {
+                    label.setIcon(null);
+                    label.setText(Objects.toString(value, ""));
+                }
+                return label;
+            }
+        });
         this.lstSimpleElement.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
             @Override
             public void valueChanged(ListSelectionEvent e) {
-                SimpleElement element = (SimpleElement) lstSimpleElement.getSelectedValue();
-                doSelectElement(element);
+                ListElement<SimpleElement> element = (ListElement<SimpleElement>) lstSimpleElement.getSelectedValue();
+                if (element != null) {
+                    doSelectElement(element.getValue());
+                }
             }
         });
         this.btnSimpleElementSortDown.addActionListener(new ActionListener() {
             @Override
             public void actionPerformed(ActionEvent e) {
                 FilteredListModel model = (FilteredListModel) lstSimpleElement.getModel();
-                model.sort(true);
+                model.sort(false);
             }
         });
         this.btnSimpleElementSortUp.addActionListener(new ActionListener() {
             @Override
             public void actionPerformed(ActionEvent e) {
                 FilteredListModel model = (FilteredListModel) lstSimpleElement.getModel();
-                model.sort(false);
+                model.sort(true);
             }
         });
+        this.txtSimpleElementFilter.addKeyListener(new KeyListener() {
+            @Override
+            public void keyTyped(KeyEvent e) {
+                if (e.getKeyChar() == '\n') {
+                    doFilter();
+                }
+            }
+
+            @Override
+            public void keyPressed(KeyEvent e) {
+            }
+
+            @Override
+            public void keyReleased(KeyEvent e) {
+            }
+        });
+        this.btnAdd.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                doInsert(" + ");
+            }
+        });
+        this.btnDiv.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                doInsert(" / ");
+            }
+        });
+        this.btnEq.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                doInsert(" = ");
+            }
+        });
+        this.btnMult.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                doInsert(" * ");
+            }
+        });
+        this.btnNeq.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                doInsert(" <> ");
+            }
+        });
+        this.btnParentClose.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                doInsert(" ) ");
+            }
+        });
+        this.btnParentOpen.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                doInsert(" ( ");
+            }
+        });
+        this.btnSubst.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                doInsert(" - ");
+            }
+        });
+        ScriptSwingManager scriptSwingManager = ToolsSwingLocator.getScriptSwingManager();
+        this.scriptPicker = scriptSwingManager.createListScriptPickerController(
+                cboPickerScripts,
+                btnPickerRemove,
+                btnPickerSelectScript
+        );
+        this.scriptEditor = scriptSwingManager.createScriptEditor();
+        this.pnlScriptEditorContainer.setLayout(new BorderLayout());
+        this.pnlScriptEditorContainer.add(this.scriptEditor.asJComponent(), BorderLayout.CENTER);
+    
+        this.setPreferredSize(new Dimension(750, 500));
     }
-    
+
+    private String removeCursorMark(String s) {
+        s = s.replace("{{", "");
+        s = s.replace("}}", "");
+        return s;
+    }
+
     private void doInsert(String s) {
-        this.txtExpression.insert(s, this.txtExpression.getCaretPosition());
+        this.txtExpression.replaceSelection(s);
+        String ss = this.txtExpression.getText();
+        int start = ss.indexOf("{{");
+        int end = ss.indexOf("}}");
+        if (start > 0 && end > 0) {
+            this.txtExpression.replaceRange("", end, end + 2);
+            this.txtExpression.replaceRange("", start, start + 2);
+            this.txtExpression.setSelectionStart(start);
+            this.txtExpression.setSelectionEnd(end - 2);
+        }
+        this.txtExpression.requestFocusInWindow();
     }
-    
+
     private void doInsert(SimpleElement element) {
-        Function function = element.getFunction();
-        if( function == null ) {
-            doInsert(element.getName());
+        if( element instanceof FunctionElement ) {
+            Function function = ((FunctionElement)element).getFunction();
+            if (function == null) {
+                doInsert(element.getName());
+            } else {
+                doInsert(function.template() == null ? function.name() + "({{}})" : function.template());
+            }
+        } else if( element instanceof ConstantElement) {
+            doInsert(" "+repr(((ConstantElement) element).getValue())+" ");
         } else {
-            doInsert(function.template());
+            doInsert(" "+element.getName()+" ");
         }
     }
-    
+
     private void doFilter() {
         FilteredListModel model = (FilteredListModel) this.lstSimpleElement.getModel();
         model.setFilter(this.txtSimpleElementFilter.getText());
     }
-    
+
     private void doSelectElement(SimpleElement element) {
         StringBuilder html = new StringBuilder();
         html.append("<html>\n");
-        html.append("<h1>").append(element.getName()).append("</h1>\n");
-        Function function = element.getFunction();
-        if( function != null ) {
-                if( StringUtils.isEmpty(function.returnType()) ) {
-                    html.append("<b>return</b> ").append(function.returnType()).append("<br>\n");
-                }
+        if( element instanceof ConstantElement ) {
+            Object value = ((ConstantElement)element).getValue();
+            if( value == null ) {
+                html.append("<b>Constant</b><br>\n<br>\n");
+                html.append("<b>Value</b> NULL<br>\n");
+            } else {
+                html.append("<b>Constant</b><br>\n<br>\n");
+                html.append("<b>Value</b> ").append(repr(value)).append("<br>\n<br>\n");
+                html.append("<b>Type</b> ").append(value.getClass().getSimpleName()).append("<br>\n<br>\n");
+            }
+            
+        } else if( element instanceof VariableElement ) {
+                VariableElement var = (VariableElement)element;
+                html.append("<b>").append(var.getLabel()).append("</b><br>\n<br>\n\n");
+                html.append("<b>Source</b> ").append(var.getSource()).append("<br>\n<br>\n");
+                html.append("<b>Name</b> ").append(var.getName()).append("<br>\n<br>\n");
+                html.append("<b>Type</b> ").append(var.getTypeName()).append("<br>\n<br>\n");
+            
+        } else if( element instanceof FunctionElement ) {
+            Function function = ((FunctionElement)element).getFunction();
+            if (function == null) {
+                html.append("<b>Function</b> ").append(element.getName()).append("<br>\n<br>\n");
+                html.append("<b>Return</b> Object<br>\n<br>\n");
+                html.append("<b>Template:</b> ").append(element.getName()).append("()<br>\n<br>\n");
+            } else {
+                html.append("<b>Function</b> ").append(element.getName()).append("<br>\n<br>\n");
+                html.append("<b>Return</b> ");
+                html.append(StringUtils.isEmpty(function.returnType())?"Objec": function.returnType());
+                html.append("<br>\n<br>\n");
+                html.append("<b>Template</b> ");
+                html.append(StringUtils.isEmpty(function.template())?element.getName()+"()": removeCursorMark(function.template()));
+                html.append("<br>\n<br>\n");
+
                 String[] args = function.descriptionArgs();
-                if( args!=null ) {
+                if (args != null) {
                     html.append("<b>Arguments:</b> ").append("<ul>\n");
                     for (String arg : args) {
                         html.append("<li>").append(arg).append("</li>\n");
@@ -197,28 +425,38 @@
                     html.append("</ul>\n");
                     html.append("<br>\n");
                 }
-                if( StringUtils.isEmpty(function.template()) ) {
-                    html.append("<b>Template:</b> ").append(function.template()).append("<br>\n");
-                }
+            }
+        } else {
+            html.append("<b>Generic element<br>\n<br>\n");
+            html.append("<b>Name</b> ").append(element.getName()).append("</br>\n</br>\n");
+            
         }
-        html.append(element.getDescription().replace("\n", "<br>")).append("<br>\n");
+
+        if( element.getDescription()!=null ) {
+            html.append("<b>Description</b><br>\n");
+            html.append(element.getDescription().replace("\n", "<br>")).append("<br>\n");
+        }
         html.append("</html>\n");
+        this.txtDescription.setContentType("text/html");
         this.txtDescription.setText(html.toString());
+        this.txtDescription.setCaretPosition(0);
     }
-    
+
     private void updateSimpleElementsList(GroupElement group) {
         FilteredListModel model = new FilteredListModel();
         model.addElement("Loading...");
         this.currentGroup = group;
         this.txtGroupElement.setText(group.getName());
-        if( group instanceof SimpleElement ) {
+        this.txtSimpleElementFilter.setText("");
+        this.txtDescription.setText("");
+        if (group instanceof SimpleElement) {
             this.btnGroupElementInsert.setEnabled(true);
         } else {
             this.btnGroupElementInsert.setEnabled(false);
         }
         this.lstSimpleElement.setEnabled(false);
         this.lstSimpleElement.setModel(model);
-        
+
         Thread th = new Thread(new Runnable() {
             @Override
             public void run() {
@@ -230,53 +468,120 @@
 
     private void simpleElementsUpdater() {
         final GroupElement group = this.currentGroup;
-        final DefaultListModel model = new DefaultListModel();
+        final FilteredListModel model = new FilteredListModel();
         for (Element child : group) {
-            if( group != this.currentGroup ) {
+            if (group != this.currentGroup) {
                 return;
             }
-            String label = child.getName();
-            if( child instanceof SimpleElement ) {
-                Function function = ((SimpleElement) child).getFunction();
-                if( function != null && StringUtils.isEmpty(function.returnType()) ) {
-                    label = "<html><b>"  + label + "</b> return " + function.returnType() + "</html>";
+            if (child instanceof SimpleElement) {
+                String label = child.getName();
+                Function function = child instanceof FunctionElement?  ((FunctionElement)child).getFunction(): null;
+                if (function != null && !StringUtils.isEmpty(function.returnType())) {
+                    label = "<html><b>" + label + "</b> return " + function.returnType() + "</html>";
+//                    label = label + " return " + function.returnType();
                 }
+                model.addElement(new ListElement<>(label, child));
             }
-            model.addElement(new ListElement<>(label,child));
         }
+        model.sort(true);
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
-                if( group != currentGroup ) {
+                if (group != currentGroup) {
                     return;
                 }
+                if( group.hasMoreElements() ) {
+                    lblSimpleElementsMsg.setText("More elements...");
+                } else {
+                    lblSimpleElementsMsg.setText("");
+                }
                 lstSimpleElement.setEnabled(true);
                 lstSimpleElement.setModel(model);
             }
         });
-        
+
     }
-    
+
     @Override
-    public String getExpression() {
-        String expression = this.txtExpression.getText();
-        if( StringUtils.isEmpty(expression) ) {
+    public Expression getExpression() {
+        String phrase = this.txtExpression.getText();
+        if (StringUtils.isEmpty(phrase)) {
             return null;
         }
+        Expression expression = ExpressionEvaluatorLocator.getManager().createExpression();
+        expression.setPhrase(phrase);
+        Script script = ToolsLocator.getScriptManager().createScript("UserScript", "", "python");
+        this.scriptEditor.fetch(script);
+        if( !StringUtils.isEmpty(script.getCode()) ) {
+            expression.setUserScript(script);
+        }
         return expression;
     }
 
     @Override
-    public List<Element> getElements() {
-        if( this.elements == null ) {
-            this.elements = manager.getElements();
+    public void setExpression(Expression expression) {
+        this.txtExpression.setText(expression.getPhrase());
+        if( expression.getUserScript()!=null ) {
+            this.scriptEditor.set(expression.getUserScript());
         }
-        return elements;
+        if( expression.getScripts()!=null ) {
+            this.scriptPicker.set(expression.getScripts().toList());
+        }
     }
     
     @Override
+    public int getSimpleElementsLimit() {
+        return this.config.getSimpleElementsLimit();
+    }
+
+    @Override
+    public void setSimpleElementsLimit(int limit) {
+        this.config.setSimpleElementsLimit(limit);
+    }
+    
+    @Override
+    public List<Element> getElements() {
+        return this.config.getElements();
+    }
+
+    @Override
+    public Collection<SymbolTable> getSymbolTables() {
+        return this.config.getSymbolTables();
+    }
+    
+    @Override
+    public void addSymbolTable(SymbolTable symbolTable) {
+        this.config.addSymbolTable(symbolTable);
+    }
+
+    @Override
+    public void removeAllSymbolTables() {
+        this.config.removeAllSymbolTables();
+    }
+
+    
+    @Override
     public JComponent asJComponent() {
+        TreeModel model = new ElementsTreeModel(
+                this.manager.createGroupElement("Elements", this.getElements())
+        );
+        this.treeElements.setModel(model);
         return this;
     }
+
+    private String repr(Object value)  {
+        if (value == null) {
+            return "NULL";
+        } else if (value instanceof Date) {
+            return "DATE('" + value + "')";
+        } else if (value instanceof Boolean) {
+            return value.toString();
+        } else if (value instanceof String) {
+            return "'" + StringEscapeUtils.escapeSql((String) value) + "'";
+        } else {
+            return StringEscapeUtils.escapeSql(value.toString());
+        }
+        
+    }
     
-}
\ No newline at end of file
+}
