cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kmen...@apache.org
Subject svn commit: r664525 - in /cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler: editor/SelectQueryMainTab.java editor/SelectQueryOrderingTab.java util/ValidatorTextAdapter.java
Date Sun, 08 Jun 2008 17:07:41 GMT
Author: kmenard
Date: Sun Jun  8 10:07:41 2008
New Revision: 664525

URL: http://svn.apache.org/viewvc?rev=664525&view=rev
Log:
Largely fixes CAY-888: CM Usability: Object Select Query Improvements.

Added:
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/ValidatorTextAdapter.java
Modified:
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryMainTab.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryOrderingTab.java

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryMainTab.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryMainTab.java?rev=664525&r1=664524&r2=664525&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryMainTab.java
(original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryMainTab.java
Sun Jun  8 10:07:41 2008
@@ -22,7 +22,10 @@
 import java.awt.BorderLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
 import java.util.Arrays;
+import java.util.Iterator;
 
 import javax.swing.DefaultComboBoxModel;
 import javax.swing.JCheckBox;
@@ -31,7 +34,10 @@
 import javax.swing.JTextField;
 
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionException;
+import org.apache.cayenne.exp.parser.ASTPath;
 import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.map.event.QueryEvent;
 import org.apache.cayenne.modeler.ProjectController;
 import org.apache.cayenne.modeler.util.CayenneWidgetFactory;
@@ -40,10 +46,12 @@
 import org.apache.cayenne.modeler.util.ExpressionConvertor;
 import org.apache.cayenne.modeler.util.ProjectUtil;
 import org.apache.cayenne.modeler.util.TextAdapter;
+import org.apache.cayenne.modeler.util.ValidatorTextAdapter;
 import org.apache.cayenne.modeler.util.combo.AutoCompletion;
 import org.apache.cayenne.query.AbstractQuery;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.util.CayenneMapEntry;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.validation.ValidationException;
 
@@ -87,12 +95,17 @@
         AutoCompletion.enable(queryRoot);
         queryRoot.setRenderer(CellRenderers.listRendererWithIcons());
 
-        qualifier = new TextAdapter(new JTextField()) {
+        qualifier = new ValidatorTextAdapter(new JTextField()) {
 
             @Override
             protected void updateModel(String text) {
                 setQueryQualifier(text);
             }
+
+            @Override
+            protected void validate(String text) throws ValidationException {
+                createQualifier(text);
+            }
         };
 
         distinct = new JCheckBox();
@@ -123,17 +136,11 @@
     }
 
     private void initController() {
-
-        queryRoot.addActionListener(new ActionListener() {
-
-            public void actionPerformed(ActionEvent event) {
-                SelectQuery query = getQuery();
-                if (query != null) {
-                    query.setRoot(queryRoot.getModel().getSelectedItem());
-                    mediator.fireQueryEvent(new QueryEvent(this, query));
-                }
-            }
-        });
+        RootSelectionHandler rootHandler = new RootSelectionHandler();
+        
+        queryRoot.addActionListener(rootHandler);
+        queryRoot.addFocusListener(rootHandler);
+        queryRoot.getEditor().getEditorComponent().addFocusListener(rootHandler);
 
         distinct.addActionListener(new ActionListener() {
 
@@ -206,19 +213,46 @@
             text = null;
         }
 
+        Expression qualifier = createQualifier(text);
+        if (qualifier != null)
+        {
+            //getQuery() is not null if we reached here
+            getQuery().setQualifier(qualifier);
+            mediator.fireQueryEvent(new QueryEvent(this, getQuery()));
+        }
+        
+    }
+    
+    /**
+     * Method to create and check an expression
+     * @param text String to be converted as Expression
+     * @return Expression if a new expression was created, null otherwise.
+     * @throws ValidationException if <code>text</code> can't be converted  
+     */
+    Expression createQualifier(String text) throws ValidationException
+    {
         SelectQuery query = getQuery();
         if (query == null) {
-            return;
+            return null;
         }
-
+        
         ExpressionConvertor convertor = new ExpressionConvertor();
         try {
             String oldQualifier = convertor.valueAsString(query.getQualifier());
             if (!Util.nullSafeEquals(oldQualifier, text)) {
                 Expression exp = (Expression) convertor.stringAsValue(text);
-                query.setQualifier(exp);
-                mediator.fireQueryEvent(new QueryEvent(this, query));
+                
+                /**
+                 * Advanced checking. See CAY-888 #1
+                 */
+                if(query.getRoot() != null) {
+                    checkExpression((Entity) query.getRoot(), exp);
+                }
+                
+                return exp;
             }
+            
+            return null;
         }
         catch (IllegalArgumentException ex) {
             // unparsable qualifier
@@ -264,4 +298,110 @@
                     + "'. Use a different name.");
         }
     }
+    
+    /**
+     * Advanced checking of an expression, needed because Expression.fromString()
+     * might terminate normally, but returned Expression will not be appliable
+     * for real Entities.
+     * Current implementation assures all attributes in expression are present in
+     * Entity  
+     * @param root Root of a query
+     * @param ex Expression to check
+     * @throws ValidationException when something's wrong
+     */
+    static void checkExpression(Entity root, Expression ex) throws ValidationException {
+        try {
+            if (ex instanceof ASTPath) {
+                /**
+                 * Try to iterate through path, if some attributes are not present,
+                 * exception will be raised
+                 */
+                
+                Iterator<CayenneMapEntry> path = root.resolvePathComponents(ex);
+                while (path.hasNext()) {
+                    path.next();
+                }
+            }
+            
+            if (ex != null) {
+                for (int i = 0; i < ex.getOperandCount(); i++) {
+                    if (ex.getOperand(i) instanceof Expression) {
+                        checkExpression(root, (Expression)ex.getOperand(i));
+                    }
+                }
+            }
+        }
+        catch (ExpressionException eex) {
+            throw new ValidationException(eex.getUnlabeledMessage());
+        }
+    }
+    
+    /**
+     * Handler to user's actions with root selection combobox
+     */
+    class RootSelectionHandler implements FocusListener, ActionListener {
+        String newName = null;
+        boolean needChangeName;
+
+        public void actionPerformed(ActionEvent ae) {
+            SelectQuery query = getQuery();
+            if (query != null) {
+                query.setRoot(queryRoot.getModel().getSelectedItem());
+                
+                if (needChangeName) { //not changed by user
+                    /**
+                     * Doing auto name change, following CAY-888 #2
+                     */
+                    Entity e = (Entity)queryRoot.getModel().getSelectedItem();
+                    
+                    String newPrefix = e.getName() + "Query";
+                    newName = newPrefix;
+                    
+                    DataMap map = mediator.getCurrentDataMap();
+                    long postfix = 1;
+                    
+                    while (map.getQuery(newName) != null) {
+                        newName = newPrefix + (postfix++);
+                    }
+                    
+                    name.setText(newName);
+                }
+            }
+        }
+
+        public void focusGained(FocusEvent e) {
+            //reset new name tracking
+            newName = null;
+            
+            SelectQuery query = getQuery();
+            if (query != null) {
+                needChangeName = hasDefaultName(query);
+            }
+            else {
+                needChangeName = false;
+            }
+        }
+
+        public void focusLost(FocusEvent e) {
+            if (newName != null) {
+                setQueryName(newName);
+            }
+            
+            newName = null;
+            needChangeName = false;
+        }
+
+        /**
+         * @return whether specified's query name is 'default' i.e. Cayenne generated
+         * A query's name is 'default' if it starts with 'UntitledQuery' or with root name.
+         * 
+         * We cannot follow user input because tab might be opened many times
+         */
+        boolean hasDefaultName(SelectQuery query) {
+            String prefix = query.getRoot() == null ? "UntitledQuery" :
+                ((Entity)query.getRoot()).getName() + "Query";
+            
+            return name.getComponent().getText().startsWith(prefix);
+        }
+    }
 }

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryOrderingTab.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryOrderingTab.java?rev=664525&r1=664524&r2=664525&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryOrderingTab.java
(original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryOrderingTab.java
Sun Jun  8 10:07:41 2008
@@ -31,6 +31,7 @@
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
 import javax.swing.JTable;
 import javax.swing.JToolBar;
 import javax.swing.ListSelectionModel;
@@ -42,6 +43,7 @@
 
 import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.map.event.QueryEvent;
+import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.ProjectController;
 import org.apache.cayenne.modeler.util.EntityTreeModel;
 import org.apache.cayenne.modeler.util.ModelerUtil;
@@ -90,9 +92,15 @@
         messagePanel = new JPanel(new BorderLayout());
         cardLayout = new CardLayout();
 
-        JPanel mainPanel = new JPanel(new BorderLayout());
-        mainPanel.add(createEditorPanel(), BorderLayout.CENTER);
-        mainPanel.add(createSelectorPanel(), BorderLayout.SOUTH);
+        /**
+         * As of CAY-888 #3 main pane is now a JSplitPane.
+         * Top component is a bit larger.
+         */
+        JSplitPane mainPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+        mainPanel.setDividerLocation(Application.getFrame().getHeight() / 2);
+        
+        mainPanel.setTopComponent(createEditorPanel());
+        mainPanel.setBottomComponent(createSelectorPanel());
 
         setLayout(cardLayout);
         add(mainPanel, REAL_PANEL);
@@ -161,7 +169,7 @@
         browser = new MultiColumnBrowser();
         browser.setPreferredColumnSize(BROWSER_CELL_DIM);
         browser.setDefaultRenderer();
-
+        
         JPanel panel = new JPanel(new BorderLayout());
         panel.add(createToolbar(), BorderLayout.NORTH);
         panel.add(new JScrollPane(
@@ -169,6 +177,10 @@
                 JScrollPane.VERTICAL_SCROLLBAR_NEVER,
                 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED), BorderLayout.CENTER);
 
+        //setting minimal size, otherwise scrolling looks awful, because of 
+        //VERTICAL_SCROLLBAR_NEVER strategy
+        panel.setMinimumSize(panel.getPreferredSize());
+        
         return panel;
     }
 
@@ -274,7 +286,7 @@
     final class OrderingModel extends AbstractTableModel {
 
         Ordering getOrdering(int row) {
-            return (Ordering) selectQuery.getOrderings().get(row);
+            return selectQuery.getOrderings().get(row);
         }
 
         public int getColumnCount() {
@@ -300,6 +312,7 @@
             }
         }
 
+        @Override
         public Class getColumnClass(int column) {
             switch (column) {
                 case 0:
@@ -312,6 +325,7 @@
             }
         }
 
+        @Override
         public String getColumnName(int column) {
             switch (column) {
                 case 0:
@@ -325,10 +339,12 @@
             }
         }
 
+        @Override
         public boolean isCellEditable(int row, int column) {
             return column == 1 || column == 2;
         }
 
+        @Override
         public void setValueAt(Object value, int row, int column) {
             Ordering ordering = getOrdering(row);
 

Added: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/ValidatorTextAdapter.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/ValidatorTextAdapter.java?rev=664525&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/ValidatorTextAdapter.java
(added)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/ValidatorTextAdapter.java
Sun Jun  8 10:07:41 2008
@@ -0,0 +1,171 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.modeler.util;
+
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.JTextComponent;
+
+import org.apache.cayenne.validation.ValidationException;
+
+/**
+ * Text adapter with live validation, which is fired in
+ * VALIDATION_DELAY time.
+ * @author Andrey Razumovsky
+ */
+public abstract class ValidatorTextAdapter extends TextAdapter {
+    /**
+     * Time between end of user input and validation firing
+     */
+    static final long VALIDATION_DELAY = 1500L;
+    
+    /**
+     * Is the live-checking enabled for the text component
+     */
+    boolean liveCheckEnabled;
+    
+    public ValidatorTextAdapter(JTextField textField) {
+        this(textField, true);
+    }
+    
+    public ValidatorTextAdapter(JTextField textField, boolean liveCheckEnabled) {
+        super(textField, true, false, true);
+        setLiveCheckEnabled(liveCheckEnabled);
+        install(textField);
+    }
+    
+    public ValidatorTextAdapter(JTextArea textArea) {
+        this(textArea, true);
+    }
+    
+    public ValidatorTextAdapter(JTextArea textArea, boolean liveCheckEnabled) {
+        super(textArea, true, false);
+        setLiveCheckEnabled(liveCheckEnabled);
+        install(textArea);
+    }
+    
+    protected void install(JTextComponent textComponent) {
+        TimerScheduler ts = new TimerScheduler();
+        
+        textComponent.getDocument().addDocumentListener(ts);
+        textComponent.addFocusListener(ts);
+    }
+    
+    /**
+     * Live-checks if text is correct
+     * @throws ValidationException if the text is incorrect
+     */
+    protected abstract void validate(String text) throws ValidationException;
+ 
+    /**
+     * @return Is the live-checking enabled for the text component
+     */
+    public boolean isLiveCheckEnabled() {
+        return liveCheckEnabled;
+    }
+    
+    /**
+     * Enables/disables live-checking
+     */
+    public void setLiveCheckEnabled(boolean b) {
+        liveCheckEnabled = b;
+    }
+    
+    /**
+     * Task to be fired after some delay 
+     */
+    class ValidationTimerTask extends TimerTask {
+        @Override
+        public void run() {
+            validate();
+        }
+    }
+    
+    protected void validate() {
+        try {
+            validate(textComponent.getText());
+            clear();
+        }
+        catch (ValidationException vex) {
+            textComponent.setBackground(errorColor);
+            textComponent.setToolTipText(vex.getUnlabeledMessage());
+        }
+    }
+    
+    /**
+     * Listener to user input, which fires validation timer 
+     */
+    class TimerScheduler implements DocumentListener, FocusListener {
+        /**
+         * The timer, which fires validation after some delay
+         */
+        Timer validationTimer;
+        
+        Object sync; //to prevent concurrent collisions
+        
+        TimerScheduler() {
+            sync = new Object();
+        }
+        
+        public void insertUpdate(DocumentEvent e) {
+            schedule();
+        }
+
+        public void changedUpdate(DocumentEvent e) {
+            schedule();
+        }
+
+        public void removeUpdate(DocumentEvent e) {
+            schedule();
+        }
+
+        void schedule() {
+            if(isLiveCheckEnabled()) {
+                synchronized (sync) {
+                    if(validationTimer != null) {
+                        validationTimer.cancel();
+                    }
+                    
+                    clear();
+                    
+                    validationTimer = new Timer("cayenne-validation-timer");
+                    validationTimer.schedule(new ValidationTimerTask(), VALIDATION_DELAY);
+                }
+            }
+        }
+
+        public void focusGained(FocusEvent e) {
+        }
+
+        public void focusLost(FocusEvent e) {
+            synchronized (sync) {
+                if(validationTimer != null) {
+                    validationTimer.cancel();
+                }
+            }
+        };
+    }
+}



Mime
View raw message