You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by km...@apache.org on 2008/08/01 20:42:57 UTC

svn commit: r681776 - in /cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler: dialog/objentity/ editor/ util/

Author: kmenard
Date: Fri Aug  1 11:42:55 2008
New Revision: 681776

URL: http://svn.apache.org/viewvc?rev=681776&view=rev
Log:
CAY-1077: ObjRelationship Mapping Dialog Improvements

Added:
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipPathBrowser.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/EntityTreeFilter.java
Modified:
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoController.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoDialog.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoModel.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjEntityRelationshipTab.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjRelationshipTableModel.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryPrefetchTab.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/EntityTreeModel.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/MultiColumnBrowser.java

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoController.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoController.java?rev=681776&r1=681775&r2=681776&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoController.java (original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoController.java Fri Aug  1 11:42:55 2008
@@ -19,25 +19,32 @@
 
 package org.apache.cayenne.modeler.dialog.objentity;
 
-import java.util.Collection;
-
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.*;
 import org.apache.cayenne.map.event.RelationshipEvent;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.ProjectController;
 import org.apache.cayenne.modeler.dialog.ResolveDbRelationshipDialog;
+import org.apache.cayenne.modeler.util.EntityTreeModel;
+import org.apache.cayenne.modeler.util.MultiColumnBrowser;
 import org.apache.cayenne.project.NamedObjectFactory;
 import org.scopemvc.controller.basic.BasicController;
 import org.scopemvc.core.Control;
 import org.scopemvc.core.ControlException;
 
+import javax.swing.*;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.TreePath;
+import java.awt.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Vector;
+
 /**
  * @since 1.1
  * @author Andrus Adamchik
  */
-public class ObjRelationshipInfoController extends BasicController {
+public class ObjRelationshipInfoController extends BasicController implements TreeSelectionListener {
 
     public static final String SAVE_CONTROL = "cayenne.modeler.mapObjRelationship.save.button";
     public static final String CANCEL_CONTROL = "cayenne.modeler.mapObjRelationship.cancel.button";
@@ -50,23 +57,41 @@
             ObjRelationship relationship) {
 
         this.mediator = mediator;
-        Collection objEntities = mediator.getCurrentDataMap().getNamespace().getObjEntities();
+        Collection<ObjEntity> objEntities = mediator.getCurrentDataMap().getNamespace().getObjEntities();
         ObjRelationshipInfoModel model = new ObjRelationshipInfoModel(
                 relationship,
                 objEntities);
         setModel(model);
     }
-
+    
     /**
      * Creates and runs the classpath dialog.
      */
+    @Override
     public void startup() {
+        /**
+         * Some workaround: need to save target first, because even if it is null,
+         * first item will be displayed in combobox. Also we do not want to have empty item
+         * in the combobox.
+         */
+        ObjRelationshipInfoModel model = (ObjRelationshipInfoModel) getModel();
+        ObjEntity target = model.getObjectTarget();
+        
         ObjRelationshipInfoDialog view = new ObjRelationshipInfoDialog();
         setView(view);
+        
+        model.setObjectTarget(target);
+        
+        /**
+         * Register auto-selection of the target
+         */
+        view.getPathBrowser().addTreeSelectionListener(this);
+        
         view.initFromModel();
         super.startup();
     }
 
+    @Override
     protected void doHandleControl(Control control) throws ControlException {
         if (control.matchesID(CANCEL_CONTROL)) {
             shutdown();
@@ -100,15 +125,19 @@
      * name, and create joins.
      */
     protected void createRelationship(boolean toMany) {
-        cancelEditing();
-
         ObjRelationshipInfoModel model = (ObjRelationshipInfoModel) getModel();
         DbEntity source = model.getStartEntity();
         DbEntity target = model.getEndEntity();
 
-        EntityRelationshipsModel selectedPathComponent = model.getSelectedPathComponent();
-        if (selectedPathComponent != null) {
-            source = (DbEntity) selectedPathComponent.getSourceEntity();
+        DbRelationship dbRel = model.getLastRelationship();
+        if (dbRel != null) {
+            source = (DbEntity) dbRel.getSourceEntity();
+        }
+        
+        if (target == null) {
+            JOptionPane.showMessageDialog((Component) getView(), "Please select target entity first.",
+                    "Warning", JOptionPane.WARNING_MESSAGE);
+            return;
         }
 
         DbRelationship dbRelationship = (DbRelationship) NamedObjectFactory
@@ -129,20 +158,54 @@
             source.removeRelationship(dbRelationship.getName());
         }
         else {
-            if (selectedPathComponent == null) {
-                selectedPathComponent = (EntityRelationshipsModel) model
-                        .getDbRelationshipPath()
-                        .get(0);
-                model.setSelectedPathComponent(selectedPathComponent);
-            }
-
-            selectedPathComponent.setRelationshipName(dbRelationship.getName());
+            MultiColumnBrowser pathBrowser = ((ObjRelationshipInfoDialog) getView()).getPathBrowser();
+            Object[] oldPath = pathBrowser.getSelectionPath() == null ?
+                    new Object[0] : pathBrowser.getSelectionPath().getPath();
+            
+            /**
+             * Update the view
+             */
+            EntityTreeModel treeModel = (EntityTreeModel) pathBrowser.getModel();
+            treeModel.invalidateChildren(source);
+            treeModel.invalidateChildren(target);
+            
+            Object[] path = new Object[Math.max(oldPath.length, 2)];
+            System.arraycopy(oldPath, 0, path, 0, path.length - 1);
+            
+            path[path.length - 1] = dbRelationship;
+            pathBrowser.setSelectionPath(new TreePath(path));
         }
 
         dialog.dispose();
     }
-
-    protected void cancelEditing() {
-        ((ObjRelationshipInfoDialog) getView()).cancelTableEditing();
+    
+    public void valueChanged(TreeSelectionEvent e) {
+        TreePath selectedPath = e.getPath();
+        
+        // first item in the path is Entity, so we must have
+        // at least two elements to constitute a valid ordering path
+        if (selectedPath == null || selectedPath.getPathCount() < 2) {
+            return;
+        }
+        
+        Relationship rel = (Relationship) selectedPath.getLastPathComponent();
+        DbEntity target = (DbEntity) rel.getTargetEntity();
+        
+        ObjRelationshipInfoModel model = (ObjRelationshipInfoModel) getModel(); 
+        
+        /**
+         * Initialize root with one of mapped ObjEntities.
+         */
+        Collection<ObjEntity> objEntities = target.getDataMap().getMappedEntities(target);
+        model.setObjectTarget(objEntities.size() == 0 ? null : objEntities.iterator().next());
+        
+        List<DbRelationship> relPath = new Vector<DbRelationship>(selectedPath.getPathCount() - 1);
+        for (int i = 1; i < selectedPath.getPathCount(); i++) {
+            relPath.add((DbRelationship) selectedPath.getPathComponent(i));
+        }
+        model.setDbRelationships(relPath);
+        
+        ((ObjRelationshipInfoDialog) getView()).updateCollectionChoosers();
     }
+
 }

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoDialog.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoDialog.java?rev=681776&r1=681775&r2=681776&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoDialog.java (original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoDialog.java Fri Aug  1 11:42:55 2008
@@ -19,38 +19,24 @@
 
 package org.apache.cayenne.modeler.dialog.objentity;
 
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-
-import javax.swing.DefaultCellEditor;
-import javax.swing.DefaultComboBoxModel;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTable;
-import javax.swing.table.TableCellEditor;
-
-import org.apache.cayenne.modeler.util.PanelFactory;
-import org.scopemvc.core.Selector;
-import org.scopemvc.view.swing.SAction;
-import org.scopemvc.view.swing.SButton;
-import org.scopemvc.view.swing.SComboBox;
-import org.scopemvc.view.swing.SLabel;
-import org.scopemvc.view.swing.SListCellRenderer;
-import org.scopemvc.view.swing.SPanel;
-import org.scopemvc.view.swing.STable;
-import org.scopemvc.view.swing.STableModel;
-import org.scopemvc.view.swing.STextField;
-import org.scopemvc.view.swing.SwingView;
-
 import com.jgoodies.forms.builder.PanelBuilder;
 import com.jgoodies.forms.layout.CellConstraints;
 import com.jgoodies.forms.layout.FormLayout;
+import org.apache.cayenne.map.Attribute;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Relationship;
+import org.apache.cayenne.modeler.util.EntityTreeFilter;
+import org.apache.cayenne.modeler.util.EntityTreeModel;
+import org.apache.cayenne.modeler.util.MultiColumnBrowser;
+import org.apache.cayenne.modeler.util.PanelFactory;
+import org.scopemvc.view.swing.*;
+
+import javax.swing.*;
+import javax.swing.tree.TreePath;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
 
 /**
  * A view of the dialog for mapping an ObjRelationship to one or more DbRelationships.
@@ -60,7 +46,12 @@
  */
 public class ObjRelationshipInfoDialog extends SPanel {
 
-    protected STable pathTable;
+    static final Dimension BROWSER_CELL_DIM = new Dimension(130, 200);
+    
+    /**
+     * Browser to select path for flattened relationship 
+     */
+    protected MultiColumnBrowser pathBrowser;
 
     protected Component collectionTypeLabel;
     protected SComboBox collectionTypeCombo;
@@ -110,28 +101,16 @@
         mapKeysCombo = new SComboBox();
         mapKeysCombo.setSelector(ObjRelationshipInfoModel.MAP_KEYS_SELECTOR);
         mapKeysCombo.setSelectionSelector(ObjRelationshipInfoModel.MAP_KEY_SELECTOR);
-
-        pathTable = new ObjRelationshipPathTable();
-        STableModel pathTableModel = new STableModel(pathTable);
-        pathTableModel
-                .setSelector(ObjRelationshipInfoModel.DB_RELATIONSHIP_PATH_SELECTOR);
-        pathTableModel.setColumnNames(new String[] {
-            "DbRelationships"
-        });
-        pathTableModel.setColumnSelectors(new Selector[] {
-            EntityRelationshipsModel.RELATIONSHIP_DISPLAY_NAME_SELECTOR
-        });
-
-        pathTable.setModel(pathTableModel);
-        pathTable
-                .setSelectionSelector(ObjRelationshipInfoModel.SELECTED_PATH_COMPONENT_SELECTOR);
-        pathTable.getColumn("DbRelationships").setCellEditor(createRelationshipEditor());
-
+        
+        pathBrowser = new ObjRelationshipPathBrowser();
+        pathBrowser.setPreferredColumnSize(BROWSER_CELL_DIM);
+        pathBrowser.setDefaultRenderer();
+        
         // enable/disable map keys for collection type selection
         collectionTypeCombo.addActionListener(new ActionListener() {
 
             public void actionPerformed(ActionEvent action) {
-                initFromModel();
+                updateCollectionChoosers();
             }
         });
 
@@ -143,7 +122,7 @@
         CellConstraints cc = new CellConstraints();
         PanelBuilder builder = new PanelBuilder(
                 new FormLayout(
-                        "right:max(50dlu;pref), 3dlu, fill:min(150dlu;pref), 3dlu, fill:min(120dlu;pref)",
+                        "right:max(50dlu;pref), 3dlu, fill:min(150dlu;pref), 3dlu, 120dlu, 3dlu, fill:min(120dlu;pref)",
                         "p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, top:14dlu, 3dlu, top:p:grow"));
         builder.setDefaultDialogBorder();
 
@@ -160,13 +139,16 @@
         builder.add(mapKeysCombo, cc.xywh(3, 11, 1, 1));
 
         builder.addSeparator("Mapping to DbRelationships", cc.xywh(1, 13, 5, 1));
-        builder.add(new JScrollPane(pathTable), cc.xywh(1, 15, 3, 3));
+        builder.add(new JScrollPane(
+                pathBrowser,
+                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED), cc.xywh(1, 15, 5, 3));
 
         JPanel newRelationshipsButtons = new JPanel(new FlowLayout(FlowLayout.LEADING));
         newRelationshipsButtons.add(newToOneButton);
         newRelationshipsButtons.add(newToManyButton);
 
-        builder.add(newRelationshipsButtons, cc.xywh(5, 15, 1, 3));
+        builder.add(newRelationshipsButtons, cc.xywh(7, 15, 1, 3));
 
         add(builder.getPanel(), BorderLayout.CENTER);
         add(PanelFactory.createButtonPanel(new JButton[] {
@@ -175,112 +157,84 @@
     }
 
     /**
-     * Cancels any editing that might be going on in the path table.
+     * @return relationship path browser
      */
-    public void cancelTableEditing() {
-        int row = pathTable.getEditingRow();
-        if (row < 0) {
-            return;
-        }
+    public MultiColumnBrowser getPathBrowser() {
+        return pathBrowser;
+    }
 
-        int column = pathTable.getEditingColumn();
-        if (column < 0) {
+    void initFromModel() {
+        // called too early in the cycle...
+        if (!updateCollectionChoosers()) {
             return;
         }
 
-        TableCellEditor editor = pathTable.getCellEditor(row, column);
-        if (editor != null) {
-            editor.cancelCellEditing();
+        ObjRelationshipInfoModel model = (ObjRelationshipInfoModel) getController()
+            .getModel();
+        
+        if (pathBrowser.getModel() == null) {
+            EntityTreeModel treeModel = new EntityTreeModel(model.getStartEntity());
+            treeModel.setFilter(
+                    new EntityTreeFilter() {
+                        public boolean attributeMatch(Object node, Attribute attr) {
+                            //attrs not allowed here
+                            return false;
+                        }
+
+                        public boolean relationshipMatch(Object node, Relationship rel) {
+                            if (!(node instanceof Relationship)) {
+                                return true;
+                            }
+                            
+                            /**
+                             * We do not allow A->B->A chains, where relationships are to-one
+                             */
+                            Relationship prev = (Relationship) node;
+                            
+                            return !(!prev.isToMany() && !rel.isToMany() &&
+                                    rel.getTargetEntity() != null &&
+                                    prev.getSourceEntity() == rel.getTargetEntity() &&
+                                    prev.getSourceEntity() != prev.getTargetEntity());
+                        }
+                        
+                    });
+        
+            pathBrowser.setModel(treeModel);
+        
+            List<DbRelationship> rels = model.getDbRelationships();
+            if (rels.size() > 0) {
+                Object[] path = new Object[rels.size() + 1];
+                path[0] = model.getStartEntity();
+            
+                System.arraycopy(rels.toArray(), 0, path, 1, rels.size());
+            
+                pathBrowser.setSelectionPath(new TreePath(path));
+            }
         }
     }
-
-    void initFromModel() {
-        // called too early in the cycle...
+    
+    /**
+     * Updates 'collection type' and 'map keys' comboboxes 
+     */
+    boolean updateCollectionChoosers() {
         if (getController() == null || getController().getModel() == null) {
-            return;
+            return false;
         }
-
+        
         ObjRelationshipInfoModel model = (ObjRelationshipInfoModel) getController()
                 .getModel();
-
+        
         boolean collectionTypeEnabled = model.isToMany();
         collectionTypeCombo.setEnabled(collectionTypeEnabled);
         collectionTypeLabel.setEnabled(collectionTypeEnabled);
-
+        
         boolean mapKeysEnabled = collectionTypeEnabled
                 && ObjRelationshipInfoModel.COLLECTION_TYPE_MAP
                         .equals(collectionTypeCombo.getSelectedItem());
         mapKeysCombo.setEnabled(mapKeysEnabled);
         mapKeysLabel.setEnabled(mapKeysEnabled);
+        
+        return true;
     }
 
-    TableCellEditor createRelationshipEditor() {
-        JComboBox relationshipCombo = new JComboBox();
-        relationshipCombo.setEditable(false);
-
-        // enable disable collections when relationship semntics changes
-        relationshipCombo.addActionListener(new ActionListener() {
-
-            public void actionPerformed(ActionEvent event) {
-                initFromModel();
-            }
-        });
-
-        return new RelationshipPicker(this, relationshipCombo);
-    }
-
-    class ObjRelationshipPathTable extends STable {
-
-        final Dimension preferredSize = new Dimension(203, 100);
-
-        ObjRelationshipPathTable() {
-            setRowHeight(25);
-            setRowMargin(3);
-        }
-
-        public Dimension getPreferredScrollableViewportSize() {
-            return preferredSize;
-        }
-    }
-
-    final class RelationshipPicker extends DefaultCellEditor {
-
-        JComboBox comboBox;
-        SwingView view;
-
-        RelationshipPicker(SwingView view, JComboBox comboBox) {
-            super(comboBox);
-            this.comboBox = comboBox;
-            this.view = view;
-        }
-
-        public Component getTableCellEditorComponent(
-                JTable table,
-                Object value,
-                boolean isSelected,
-                int row,
-                int column) {
-
-            // initialize combo box
-            ObjRelationshipInfoModel model = (ObjRelationshipInfoModel) view
-                    .getBoundModel();
-
-            EntityRelationshipsModel relationshipWrapper = (EntityRelationshipsModel) model
-                    .getDbRelationshipPath()
-                    .get(row);
-
-            DefaultComboBoxModel comboModel = new DefaultComboBoxModel(
-                    relationshipWrapper.getRelationshipNames());
-            comboModel.setSelectedItem(value);
-            comboBox.setModel(comboModel);
-
-            // call super
-            return super.getTableCellEditorComponent(
-                    table,
-                    value,
-                    isSelected,
-                    row,
-                    column);
-        }
-    }
 }

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoModel.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoModel.java?rev=681776&r1=681775&r2=681776&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoModel.java (original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipInfoModel.java Fri Aug  1 11:42:55 2008
@@ -24,12 +24,9 @@
 import org.apache.cayenne.modeler.util.Comparators;
 import org.apache.cayenne.modeler.util.DeleteRuleUpdater;
 import org.apache.cayenne.util.Util;
-import org.scopemvc.core.IntIndexSelector;
-import org.scopemvc.core.ModelChangeEvent;
 import org.scopemvc.core.ModelChangeTypes;
 import org.scopemvc.core.Selector;
 import org.scopemvc.model.basic.BasicModel;
-import org.scopemvc.model.collection.ListModel;
 
 import java.util.*;
 
@@ -42,14 +39,15 @@
 public class ObjRelationshipInfoModel extends BasicModel {
 
     static final String COLLECTION_TYPE_MAP = "java.util.Map";
+    static final String COLLECTION_TYPE_SET = "java.util.Set";
+    static final String COLLECTION_TYPE_COLLECTION = "java.util.Collection";
+    
     static final String DEFAULT_MAP_KEY = "ID (default)";
 
-    public static final Selector DB_RELATIONSHIP_PATH_SELECTOR = Selector
-            .fromString("dbRelationshipPath");
+    public static final Selector DB_RELATIONSHIPS_SELECTOR = Selector
+            .fromString("dbRelationships");
     public static final Selector SOURCE_ENTITY_NAME_SELECTOR = Selector
             .fromString("relationship.sourceEntity.name");
-    public static final Selector SELECTED_PATH_COMPONENT_SELECTOR = Selector
-            .fromString("selectedPathComponent");
     public static final Selector OBJECT_TARGET_SELECTOR = Selector
             .fromString("objectTarget");
     public static final Selector OBJECT_TARGETS_SELECTOR = Selector
@@ -64,21 +62,25 @@
     public static final Selector MAP_KEY_SELECTOR = Selector.fromString("mapKey");
 
     protected ObjRelationship relationship;
-    protected ListModel dbRelationshipPath;
-    protected EntityRelationshipsModel selectedPathComponent;
+    
+    /**
+     * List of DB Relationships current ObjRelationship is mapped to
+     */
+    protected List<DbRelationship> dbRelationships;
+    
     protected ObjEntity objectTarget;
-    protected List objectTargets;
-    protected List targetCollections;
+    protected List<ObjEntity> objectTargets;
+    protected List<String> targetCollections;
     protected List mapKeys;
     protected String relationshipName;
     protected String targetCollection;
     protected String mapKey;
 
-    public ObjRelationshipInfoModel(ObjRelationship relationship, Collection objEntities) {
+    public ObjRelationshipInfoModel(ObjRelationship relationship, Collection<ObjEntity> objEntities) {
 
         this.relationship = relationship;
         this.relationshipName = relationship.getName();
-        this.objectTarget = (ObjEntity) relationship.getTargetEntity();
+        
         this.mapKey = relationship.getMapKey();
         this.targetCollection = relationship.getCollectionType();
         if (targetCollection == null) {
@@ -87,14 +89,15 @@
 
         // prepare entities - copy those that have DbEntities mapped, and then sort
 
-        this.objectTargets = new ArrayList(objEntities.size());
-        Iterator entities = objEntities.iterator();
+        this.objectTargets = new ArrayList<ObjEntity>(objEntities.size());
+        Iterator<ObjEntity> entities = objEntities.iterator();
         while (entities.hasNext()) {
-            ObjEntity entity = (ObjEntity) entities.next();
+            ObjEntity entity = entities.next();
             if (entity.getDbEntity() != null) {
                 objectTargets.add(entity);
             }
         }
+        this.objectTarget = (ObjEntity) relationship.getTargetEntity();
 
         Collections.sort(objectTargets, Comparators.getNamedObjectComparator());
 
@@ -103,52 +106,48 @@
         // and target entities present, with DbEntities chosen.
         validateCanMap();
 
-        this.targetCollections = new ArrayList(4);
-        targetCollections.add("java.util.Collection");
+        this.targetCollections = new ArrayList<String>(4);
+        targetCollections.add(COLLECTION_TYPE_COLLECTION);
         targetCollections.add(ObjRelationship.DEFAULT_COLLECTION_TYPE);
         targetCollections.add(COLLECTION_TYPE_MAP);
-        targetCollections.add("java.util.Set");
+        targetCollections.add(COLLECTION_TYPE_SET);
 
         this.mapKeys = new ArrayList();
         initMapKeys();
 
-        // wrap path
-        this.dbRelationshipPath = new ListModel();
-        Iterator it = relationship.getDbRelationships().iterator();
-        while (it.hasNext()) {
-            DbRelationship dbRelationship = (DbRelationship) it.next();
-            this.dbRelationshipPath.add(new EntityRelationshipsModel(dbRelationship));
-        }
+        // setup path
+        dbRelationships = new ArrayList<DbRelationship>(relationship.getDbRelationships());
 
         // this sets the right enabled state of collection type selectors
-        fireModelChange(ModelChangeTypes.VALUE_CHANGED, DB_RELATIONSHIP_PATH_SELECTOR);
+        fireModelChange(ModelChangeTypes.VALUE_CHANGED, DB_RELATIONSHIPS_SELECTOR);
 
         // add dummy last relationship if we are not connected
         connectEnds();
-        this.dbRelationshipPath.addModelChangeListener(this);
     }
 
     public ObjRelationship getRelationship() {
         return relationship;
     }
-
-    public ListModel getDbRelationshipPath() {
-        return dbRelationshipPath;
+    
+    /**
+     * @return list of DB Relationships current ObjRelationship is mapped to 
+     */
+    public List<DbRelationship> getDbRelationships() {
+        return dbRelationships;
     }
-
-    public EntityRelationshipsModel getSelectedPathComponent() {
-        return selectedPathComponent;
+    
+    /**
+     * @return last relationship in the path, or <code>null</code> if path is empty
+     */
+    public DbRelationship getLastRelationship() {
+        return dbRelationships.size() == 0 ? null : dbRelationships.get(dbRelationships.size() - 1);
     }
-
-    public void setSelectedPathComponent(EntityRelationshipsModel selectedPathComponent) {
-        if (this.selectedPathComponent != selectedPathComponent) {
-            unlistenOldSubmodel(SELECTED_PATH_COMPONENT_SELECTOR);
-            this.selectedPathComponent = selectedPathComponent;
-            listenNewSubmodel(SELECTED_PATH_COMPONENT_SELECTOR);
-            fireModelChange(
-                    ModelChangeTypes.VALUE_CHANGED,
-                    SELECTED_PATH_COMPONENT_SELECTOR);
-        }
+    
+    /**
+     * Sets list of DB Relationships current ObjRelationship is mapped to
+     */
+    public void setDbRelationships(List<DbRelationship> rels) {
+        this.dbRelationships = rels;
     }
 
     /**
@@ -168,11 +167,6 @@
             listenNewSubmodel(OBJECT_TARGET_SELECTOR);
             fireModelChange(ModelChangeTypes.VALUE_CHANGED, OBJECT_TARGET_SELECTOR);
 
-            // change the list of relationships
-            breakChain(-1);
-            connectEnds();
-            fireModelChange(ModelChangeTypes.VALUE_CHANGED, DB_RELATIONSHIP_PATH_SELECTOR);
-
             // init available map keys
             initMapKeys();
         }
@@ -182,6 +176,13 @@
         this.mapKeys.clear();
 
         mapKeys.add(DEFAULT_MAP_KEY);
+        
+        /**
+         * Object target can be null when selected target DbEntity has no ObjEntities
+         */
+        if (objectTarget == null) {
+            return;
+        }
 
         Iterator attributes = this.objectTarget.getAttributes().iterator();
         while (attributes.hasNext()) {
@@ -200,10 +201,10 @@
     /**
      * Returns a list of ObjEntities available for target mapping.
      */
-    public List getObjectTargets() {
+    public List<ObjEntity> getObjectTargets() {
         return objectTargets;
     }
-
+   
     public String getRelationshipName() {
         return relationshipName;
     }
@@ -212,24 +213,6 @@
         this.relationshipName = relationshipName;
     }
 
-    @Override
-    public void modelChanged(ModelChangeEvent event) {
-
-        // if a different relationship was selected, we may need to rebuild the list
-        Selector selector = event.getSelector();
-        while (selector != null) {
-            if (selector instanceof IntIndexSelector) {
-                IntIndexSelector indexSel = (IntIndexSelector) selector;
-                relationshipChanged(indexSel.getIndex());
-                break;
-            }
-
-            selector = selector.getNext();
-        }
-
-        super.modelChanged(event);
-    }
-
     /**
      * Processes relationship path when path component at index was changed.
      */
@@ -241,18 +224,17 @@
         connectEnds();
 
         // must fire with null selector, or refresh won't happen
-        dbRelationshipPath.fireModelChange(VALUE_CHANGED, null);
+        fireModelChange(VALUE_CHANGED, null);
     }
 
     public boolean isToMany() {
         // copied algorithm from ObjRelationship.calculateToMany(), only iterating through
         // the unsaved dbrels selection.
 
-        Iterator dbRelIterator = dbRelationshipPath.iterator();
+        Iterator<DbRelationship> dbRelIterator = dbRelationships.iterator();
         while (dbRelIterator.hasNext()) {
-            EntityRelationshipsModel next = (EntityRelationshipsModel) dbRelIterator
-                    .next();
-            Relationship relationship = next.getSelectedRelationship();
+            
+            DbRelationship relationship = dbRelIterator.next();
             if (relationship != null && relationship.isToMany()) {
                 return true;
             }
@@ -273,8 +255,24 @@
             hasChanges = true;
             relationship.setName(relationshipName);
         }
-
-        if (!Util.nullSafeEquals(objectTarget.getName(), relationship
+        
+        if (dbRelationships.size() > 0) {
+            DbEntity lastEntity = (DbEntity)
+                dbRelationships.get(dbRelationships.size() - 1).getTargetEntity();
+
+            if (objectTarget == null || objectTarget.getDbEntity() != lastEntity) {
+                /**
+                 * Entities in combobox and path browser do not match.
+                 * In this case, we rely on the browser and automatically select one
+                 * of lastEntity's ObjEntities
+                 */
+                Collection<ObjEntity> objEntities = 
+                    lastEntity.getDataMap().getMappedEntities(lastEntity);
+                objectTarget = objEntities.size() == 0 ? null : objEntities.iterator().next();
+            }
+        }
+        
+        if (objectTarget == null || !Util.nullSafeEquals(objectTarget.getName(), relationship
                 .getTargetEntityName())) {
             hasChanges = true;
 
@@ -285,16 +283,16 @@
         }
 
         // check for path modifications
-        List oldPath = relationship.getDbRelationships();
-        if (oldPath.size() != dbRelationshipPath.size()) {
+        List<DbRelationship> oldPath = relationship.getDbRelationships();
+        if (oldPath.size() != dbRelationships.size()) {
             hasChanges = true;
             updatePath();
         }
         else {
             for (int i = 0; i < oldPath.size(); i++) {
-                EntityRelationshipsModel next = (EntityRelationshipsModel) dbRelationshipPath
-                        .get(i);
-                if (oldPath.get(i) != next.getSelectedRelationship()) {
+                DbRelationship next = dbRelationships.get(i);
+                
+                if (oldPath.get(i) != next) {
                     hasChanges = true;
                     updatePath();
                     break;
@@ -332,10 +330,9 @@
     private void updatePath() {
         relationship.clearDbRelationships();
 
-        Iterator it = dbRelationshipPath.iterator();
+        Iterator<DbRelationship> it = dbRelationships.iterator();
         while (it.hasNext()) {
-            EntityRelationshipsModel next = (EntityRelationshipsModel) it.next();
-            Relationship nextPathComponent = next.getSelectedRelationship();
+            Relationship nextPathComponent = it.next();
             if (nextPathComponent == null) {
                 break;
             }
@@ -346,16 +343,10 @@
 
     private void breakChain(int index) {
         // strip everything starting from the index
-        dbRelationshipPath.makeActive(false);
 
-        try {
-            while (dbRelationshipPath.size() > (index + 1)) {
-                // remove last
-                dbRelationshipPath.remove(dbRelationshipPath.size() - 1);
-            }
-        }
-        finally {
-            dbRelationshipPath.makeActive(true);
+        while (dbRelationships.size() > (index + 1)) {
+            // remove last
+            dbRelationships.remove(dbRelationships.size() - 1);
         }
     }
 
@@ -364,17 +355,14 @@
     private void connectEnds() {
         Relationship last = null;
 
-        int size = dbRelationshipPath.size();
+        int size = dbRelationships.size();
         if (size > 0) {
-            EntityRelationshipsModel wrapper = (EntityRelationshipsModel) dbRelationshipPath
-                    .get(size - 1);
-            last = wrapper.getSelectedRelationship();
-
+            last = dbRelationships.get(size - 1);
         }
 
         Entity target = getEndEntity();
 
-        if (last == null || last.getTargetEntity() != target) {
+        if (target != null && (last == null || last.getTargetEntity() != target)) {
             // try to connect automatically, if we can't use dummy connector
 
             Entity source = (last == null) ? getStartEntity() : last.getTargetEntity();
@@ -382,43 +370,29 @@
 
                 Relationship anyConnector = source != null ? source
                         .getAnyRelationship(target) : null;
-                EntityRelationshipsModel connector = null;
-
-                connector = (anyConnector == null) ? new EntityRelationshipsModel(
-                        source,
-                        getEndEntity()) : new EntityRelationshipsModel(anyConnector);
-
-                dbRelationshipPath.makeActive(false);
-                try {
-                    dbRelationshipPath.add(connector);
-                }
-                finally {
-                    dbRelationshipPath.makeActive(true);
+                
+                if (anyConnector != null) {
+                    dbRelationships.add((DbRelationship) anyConnector);
                 }
             }
         }
     }
 
+    /**
+     * Checks if the entity can be edited with this inspector.
+     * NOTE: As of CAY-1077, relationship inspector can be opened even if no target entity 
+     * was set.
+     */
     private void validateCanMap() {
         if (relationship.getSourceEntity() == null) {
             throw new CayenneRuntimeException(
                     "Can't map relationship without source entity.");
         }
 
-        if (relationship.getTargetEntity() == null) {
-            throw new CayenneRuntimeException(
-                    "Can't map relationship without target entity.");
-        }
-
         if (getStartEntity() == null) {
             throw new CayenneRuntimeException(
                     "Can't map relationship without source DbEntity.");
         }
-
-        if (getEndEntity() == null) {
-            throw new CayenneRuntimeException(
-                    "Can't map relationship without target DbEntity.");
-        }
     }
 
     public DbEntity getStartEntity() {
@@ -426,6 +400,13 @@
     }
 
     public DbEntity getEndEntity() {
+        /**
+         * Object target can be null when selected target DbEntity has no ObjEntities
+         */
+        if (objectTarget == null) {
+            return null;
+        }
+        
         return objectTarget.getDbEntity();
     }
 
@@ -449,7 +430,7 @@
         return mapKeys;
     }
 
-    public List getTargetCollections() {
+    public List<String> getTargetCollections() {
         return targetCollections;
     }
 }

Added: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipPathBrowser.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipPathBrowser.java?rev=681776&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipPathBrowser.java (added)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/ObjRelationshipPathBrowser.java Fri Aug  1 11:42:55 2008
@@ -0,0 +1,137 @@
+/*****************************************************************
+ *   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.dialog.objentity;
+
+import org.apache.cayenne.modeler.util.MultiColumnBrowser;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.tree.TreePath;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+/**
+ * Multi-column browser for obj relationships
+ * @author Andrey Razumovsky
+ */
+public class ObjRelationshipPathBrowser extends MultiColumnBrowser {
+    /**
+     * Listener, which performs adding of new column
+     */
+    private MouseListener panelOpener;
+    
+    /**
+     * Listener, which performs removing of columns to the right of selected row
+     */
+    private ListSelectionListener panelRemover;
+    
+    public ObjRelationshipPathBrowser() {
+        this(DEFAULT_MIN_COLUMNS_COUNT);
+    }
+
+    public ObjRelationshipPathBrowser(int minColumns) {
+        super(minColumns);
+    }
+    
+    @Override
+    protected void installColumn(BrowserPanel panel) {
+        panel.setToolTipText("Double-click on row to extend path");
+        
+        if (panelOpener == null) {
+            panelOpener = new PanelOpener();
+        }
+        
+        if (panelRemover == null) {
+            panelRemover = new PanelRemover();
+        }
+        
+        panel.addMouseListener(panelOpener);
+        panel.addListSelectionListener(panelRemover);
+        
+        panel.setCellRenderer(renderer);
+    }
+    
+    /**
+     * Selects one path component.
+     * Need to override this method, because list selection does not cause loading
+     * in this browser.
+     */
+    @Override
+    protected void selectRow(Object row, int index, TreePath path) {
+        if (index > 0 && columns.get(index - 1).getSelectedValue() != row) {
+            columns.get(index - 1).setSelectedValue(row, true);
+        }
+
+        if (index != path.getPathCount() - 1) {
+            updateFromModel(row, index - 1);
+        }
+    }
+    
+    @Override
+    protected void uninstallColumn(BrowserPanel panel) {
+        panel.removeMouseListener(panelOpener);
+        panel.removeListSelectionListener(panelRemover);
+    }
+    
+    /**
+     * Listener, which performs adding of new column at double-click
+     */
+    protected class PanelOpener extends MouseAdapter {
+        /**
+         * Invoked when the mouse has been clicked on a component.
+         */
+        public void mouseClicked(MouseEvent e) {
+            if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
+                process(e);
+            }
+        }
+               
+        private void process(MouseEvent e) {
+            BrowserPanel panel = (BrowserPanel) e.getSource();
+            Object selectedNode = panel.getSelectedValue();
+
+            // ignore unselected
+            if (selectedNode != null) {
+                updateFromModel(selectedNode, columns.indexOf(panel));
+            }
+        }
+    }
+    
+    /**
+     * Listener, which performs removing columns to the right of selected row
+     */
+    protected class PanelRemover implements ListSelectionListener {
+
+        public void valueChanged(ListSelectionEvent e) {
+         // ignore "adjusting"
+            if (!e.getValueIsAdjusting()) {
+                BrowserPanel panel = (BrowserPanel) e.getSource();
+                
+                Object selectedNode = panel.getSelectedValue();
+                
+                if (selectedNode != null) {
+                    updateFromModel(selectedNode, columns.indexOf(panel), false);
+                }
+            }
+        }
+        
+    }
+}

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjEntityRelationshipTab.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjEntityRelationshipTab.java?rev=681776&r1=681775&r2=681776&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjEntityRelationshipTab.java (original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjEntityRelationshipTab.java Fri Aug  1 11:42:55 2008
@@ -19,34 +19,6 @@
 
 package org.apache.cayenne.modeler.editor;
 
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.Collection;
-import java.util.EventObject;
-import java.util.List;
-
-import javax.swing.DefaultCellEditor;
-import javax.swing.DefaultComboBoxModel;
-import javax.swing.Icon;
-import javax.swing.JButton;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JMenuItem;
-import javax.swing.JPanel;
-import javax.swing.JPopupMenu;
-import javax.swing.JTable;
-import javax.swing.JToolBar;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-import javax.swing.event.TableModelEvent;
-import javax.swing.event.TableModelListener;
-import javax.swing.table.DefaultTableCellRenderer;
-import javax.swing.table.TableColumn;
-
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DeleteRule;
 import org.apache.cayenne.map.ObjEntity;
@@ -65,17 +37,22 @@
 import org.apache.cayenne.modeler.event.ObjEntityDisplayListener;
 import org.apache.cayenne.modeler.event.RelationshipDisplayEvent;
 import org.apache.cayenne.modeler.event.TablePopupHandler;
-import org.apache.cayenne.modeler.util.CayenneAction;
-import org.apache.cayenne.modeler.util.CayenneTable;
-import org.apache.cayenne.modeler.util.CayenneWidgetFactory;
-import org.apache.cayenne.modeler.util.CellRenderers;
-import org.apache.cayenne.modeler.util.ModelerUtil;
-import org.apache.cayenne.modeler.util.PanelFactory;
-import org.apache.cayenne.modeler.util.UIUtil;
+import org.apache.cayenne.modeler.util.*;
 import org.apache.cayenne.modeler.util.combo.AutoCompletion;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableColumn;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import java.util.EventObject;
+import java.util.List;
+
 /**
  * Displays ObjRelationships for the edited ObjEntity.
  * 
@@ -176,6 +153,11 @@
                         .getModel();
                 new ObjRelationshipInfoController(mediator, model.getRelationship(row))
                         .startup();
+                
+                /**
+                 * This is required for a table to be updated properly
+                 */
+                table.cancelEditing();
 
                 // need to refresh selected row... do this by unselecting/selecting the
                 // row
@@ -239,9 +221,10 @@
                 rels[i] = model.getRelationship(sel[i]);
             }
             
-            if (rels.length == 1 && rels[0].getTargetEntity() != null
-                    && ((ObjEntity) rels[0].getSourceEntity()).getDbEntity() != null
-                    && ((ObjEntity) rels[0].getTargetEntity()).getDbEntity() != null) {
+            /**
+             * As of CAY-1077, relationship inspector can be opened even if no target entity was set.
+             */
+            if (rels.length == 1 && ((ObjEntity) rels[0].getSourceEntity()).getDbEntity() != null) {
                 resolve.setEnabled(true);
             }
             else

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjRelationshipTableModel.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjRelationshipTableModel.java?rev=681776&r1=681775&r2=681776&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjRelationshipTableModel.java (original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/ObjRelationshipTableModel.java Fri Aug  1 11:42:55 2008
@@ -19,22 +19,17 @@
 
 package org.apache.cayenne.modeler.editor;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.DeleteRule;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.map.Relationship;
+import org.apache.cayenne.map.*;
 import org.apache.cayenne.map.event.RelationshipEvent;
 import org.apache.cayenne.modeler.ProjectController;
 import org.apache.cayenne.modeler.util.CayenneTableModel;
 import org.apache.cayenne.modeler.util.ProjectUtil;
 import org.apache.cayenne.util.Util;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
 /**
  * Table model to display ObjRelationships.
  * 
@@ -61,6 +56,7 @@
         Collections.sort(objectList, new RelationshipComparator());
     }
 
+    @Override
     protected void orderList() {
         // NOOP
     }
@@ -72,6 +68,7 @@
     /**
      * Returns ObjRelationship class.
      */
+    @Override
     public Class getElementsClass() {
         return ObjRelationship.class;
     }
@@ -80,6 +77,7 @@
         return 5;
     }
 
+    @Override
     public String getColumnName(int column) {
         switch (column) {
             case REL_NAME:
@@ -98,6 +96,7 @@
         }
     }
 
+    @Override
     public Class getColumnClass(int col) {
         switch (col) {
             case REL_TARGET:
@@ -155,6 +154,7 @@
         }
     }
 
+    @Override
     public void setUpdatedValueAt(Object value, int row, int column) {
         ObjRelationship relationship = getRelationship(row);
         RelationshipEvent event = new RelationshipEvent(eventSource, relationship, entity);
@@ -168,7 +168,12 @@
         else if (column == REL_TARGET) {
             ObjEntity target = (ObjEntity) value;
             relationship.setTargetEntity(target);
-
+            
+            /**
+             * Clear existing relationships, otherwise addDbRelationship() might fail
+             */
+            relationship.clearDbRelationships();
+            
             // now try to connect DbEntities if we can do it in one step
             if (target != null) {
                 DbEntity srcDB = ((ObjEntity) relationship.getSourceEntity())
@@ -214,6 +219,7 @@
         return (relationship != null) ? relationship.getSourceEntity() != entity : false;
     }
 
+    @Override
     public boolean isCellEditable(int row, int col) {
         return !isInherited(row) && col != REL_SEMANTICS;
     }

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryPrefetchTab.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryPrefetchTab.java?rev=681776&r1=681775&r2=681776&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryPrefetchTab.java (original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/editor/SelectQueryPrefetchTab.java Fri Aug  1 11:42:55 2008
@@ -19,28 +19,27 @@
 
 package org.apache.cayenne.modeler.editor;
 
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.Collection;
-import java.util.Iterator;
-
-import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JToolBar;
-import javax.swing.table.AbstractTableModel;
-import javax.swing.table.TableModel;
-import javax.swing.tree.TreeModel;
-
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionException;
+import org.apache.cayenne.map.Attribute;
 import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.map.Relationship;
 import org.apache.cayenne.map.event.QueryEvent;
 import org.apache.cayenne.modeler.ProjectController;
+import org.apache.cayenne.modeler.util.EntityTreeFilter;
 import org.apache.cayenne.modeler.util.EntityTreeModel;
 import org.apache.cayenne.modeler.util.ModelerUtil;
 import org.apache.cayenne.query.PrefetchTreeNode;
 
+import javax.swing.*;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableModel;
+import javax.swing.tree.TreeModel;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import java.util.Iterator;
+
 /**
  * Subclass of the SelectQueryOrderingTab configured to work with prefetches.
  * 
@@ -82,7 +81,16 @@
 
     protected TreeModel createBrowserModel(Entity entity) {
         EntityTreeModel treeModel = new EntityTreeModel(entity);
-        treeModel.setHideAttributes(true);
+        treeModel.setFilter(
+                new EntityTreeFilter() {
+                    public boolean attributeMatch(Object node, Attribute attr) {
+                        return false;
+                    }
+
+                    public boolean relationshipMatch(Object node, Relationship rel) {
+                        return true;
+                    }
+                });
         return treeModel;
     }
 

Added: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/EntityTreeFilter.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/EntityTreeFilter.java?rev=681776&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/EntityTreeFilter.java (added)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/EntityTreeFilter.java Fri Aug  1 11:42:55 2008
@@ -0,0 +1,39 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.map.Attribute;
+import org.apache.cayenne.map.Relationship;
+
+/**
+ * EntityTreeFilter is an interface for deciding which attributes or
+ * relationships should appear in the tree
+ * @author Andrey Razumovsky
+ */
+public interface EntityTreeFilter {
+    /**
+     * Checks if attribute should appear in the tree 
+     */
+    public boolean attributeMatch(Object node, Attribute attr);
+    
+    /**
+     * Checks if relationship should appear in the tree 
+     */
+    public boolean relationshipMatch(Object node, Relationship rel);
+}

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/EntityTreeModel.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/EntityTreeModel.java?rev=681776&r1=681775&r2=681776&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/EntityTreeModel.java (original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/EntityTreeModel.java Fri Aug  1 11:42:55 2008
@@ -19,20 +19,14 @@
 
 package org.apache.cayenne.modeler.util;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
+import org.apache.cayenne.map.Attribute;
+import org.apache.cayenne.map.Entity;
+import org.apache.cayenne.map.Relationship;
 
 import javax.swing.event.TreeModelListener;
 import javax.swing.tree.TreeModel;
 import javax.swing.tree.TreePath;
-
-import org.apache.cayenne.map.Attribute;
-import org.apache.cayenne.map.Entity;
-import org.apache.cayenne.map.Relationship;
+import java.util.*;
 
 /**
  * Swing TreeModel for Entity attributes and relationships
@@ -42,29 +36,22 @@
  */
 public class EntityTreeModel implements TreeModel {
     protected Entity root;
-    protected Map<String, Object[]> sortedChildren;
-
-    // TODO: in the future replace with a more generic filter 
-    // to allow arbitrary tree customization
-    protected boolean hideAttributes;
+    protected Map<Object, Object[]> sortedChildren;
 
+    /**
+     * Filter for checking attributes and relationships
+     */
+    protected EntityTreeFilter filter;
+    
     public EntityTreeModel(Entity root) {
         this.root = root;
-        sortedChildren = Collections.synchronizedMap(new HashMap<String, Object[]>());
+        sortedChildren = Collections.synchronizedMap(new HashMap<Object, Object[]>());
     }
 
     public Object getRoot() {
         return root;
     }
 
-    public boolean isHideAttributes() {
-        return hideAttributes;
-    }
-
-    public void setHideAttributes(boolean hideAttributes) {
-        this.hideAttributes = hideAttributes;
-    }
-
     public Object getChild(Object node, int index) {
         return sortedChildren(node)[index];
     }
@@ -111,30 +98,36 @@
         }
 
         synchronized (sortedChildren) {
-            String key = entity.getName();
-            Object[] sortedForNode = (Object[]) sortedChildren.get(key);
+            Object key = node;
+            Object[] sortedForNode = sortedChildren.get(key);
 
             if (sortedForNode == null) {
                 Collection<? extends Attribute> attributes = entity.getAttributes();
                 Collection<? extends Relationship> relationships = entity.getRelationships();
 
+                List<Object> nodes = new Vector<Object>();
+                                
                 // combine two collections in an array
-                int alen = (hideAttributes) ? 0 : attributes.size();
-                int rlen = relationships.size();
-                sortedForNode = new Object[alen + rlen];
-
-                if (!hideAttributes) {
-                    Iterator<? extends Attribute> ait = attributes.iterator();
-                    for (int i = 0; i < alen; i++) {
-                        sortedForNode[i] = ait.next();
+                Iterator<? extends Attribute> ait = attributes.iterator();
+                while (ait.hasNext()) {
+                    Attribute attr = ait.next(); 
+                    
+                    if (filter == null || filter.attributeMatch(node, attr)) {
+                        nodes.add(attr);
                     }
                 }
 
                 Iterator<? extends Relationship> rit = relationships.iterator();
-                for (int i = 0; i < rlen; i++) {
-                    sortedForNode[alen + i] = rit.next();
+                while (rit.hasNext()) {
+                    Relationship rel = rit.next();
+                    
+                    if (filter == null || filter.relationshipMatch(node, rel)) {
+                        nodes.add(rel);
+                    }
                 }
 
+                sortedForNode = nodes.toArray();
+                
                 Arrays.sort(sortedForNode, Comparators.getEntityChildrenComparator());
                 sortedChildren.put(key, sortedForNode);
             }
@@ -142,6 +135,15 @@
             return sortedForNode;
         }
     }
+    
+    /**
+     * Removes children cache for specified entity.
+     */
+    public void invalidateChildren(Entity entity) {
+        synchronized (sortedChildren) {
+            sortedChildren.remove(entity.getName());
+        }
+    }
 
     private Entity entityForNonLeafNode(Object node) {
         if (node instanceof Entity) {
@@ -154,4 +156,18 @@
         String className = (node != null) ? node.getClass().getName() : "null";
         throw new IllegalArgumentException("Unexpected non-leaf node: " + className);
     }
+    
+    /**
+     * Sets filter for attrs and rels
+     */
+    public void setFilter(EntityTreeFilter filter) {
+        this.filter = filter;
+    }
+    
+    /**
+     * Returns filter for attrs and rels
+     */
+    public EntityTreeFilter getFilter() {
+        return filter;
+    }
 }

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/MultiColumnBrowser.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/MultiColumnBrowser.java?rev=681776&r1=681775&r2=681776&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/MultiColumnBrowser.java (original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/MultiColumnBrowser.java Fri Aug  1 11:42:55 2008
@@ -19,26 +19,7 @@
 
 package org.apache.cayenne.modeler.util;
 
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.GridLayout;
-import java.awt.Rectangle;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import javax.swing.AbstractListModel;
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.ImageIcon;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JViewport;
-import javax.swing.ListCellRenderer;
-import javax.swing.ListSelectionModel;
+import javax.swing.*;
 import javax.swing.border.Border;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
@@ -46,6 +27,11 @@
 import javax.swing.event.TreeSelectionListener;
 import javax.swing.tree.TreeModel;
 import javax.swing.tree.TreePath;
+import java.awt.*;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 
 /**
@@ -71,7 +57,7 @@
  */
 public class MultiColumnBrowser extends JPanel {
 
-    private static final ImageIcon rightArrow =
+    protected static final ImageIcon rightArrow =
         ModelerUtil.buildIcon("scroll_right.gif");
 
     public static final int DEFAULT_MIN_COLUMNS_COUNT = 3;
@@ -82,9 +68,14 @@
     protected Object[] selectionPath;
     protected Dimension preferredColumnSize;
 
-    private List<BrowserPanel> columns;
-    private ListSelectionListener browserSelector;
-    private List<TreeSelectionListener> treeSelectionListeners;
+    protected List<BrowserPanel> columns;
+    protected ListSelectionListener browserSelector;
+    protected List<TreeSelectionListener> treeSelectionListeners;
+    
+    /**
+     * Whether firing of TreeSelectionListeners is disabled now
+     */
+    private boolean fireDisabled;
 
     public MultiColumnBrowser() {
         this(DEFAULT_MIN_COLUMNS_COUNT);
@@ -123,16 +114,15 @@
      * Notifies listeners of a tree selection change.
      */
     protected void fireTreeSelectionEvent(Object[] selectionPath) {
+        if (fireDisabled) {
+            return;
+        }
+        
         TreeSelectionEvent e =
             new TreeSelectionEvent(this, new TreePath(selectionPath), false, null, null);
         synchronized (treeSelectionListeners) {
             for (TreeSelectionListener listener : treeSelectionListeners)
                 listener.valueChanged(e);
-//            Iterator<TreeSelectionListener> it = treeSelectionListeners.iterator();
-//            while (it.hasNext()) {
-//                TreeSelectionListener listener = (TreeSelectionListener) it.next();
-//                listener.valueChanged(e);
-//            }
         }
     }
 
@@ -142,6 +132,34 @@
     public TreePath getSelectionPath() {
         return new TreePath(selectionPath);
     }
+    
+    /**
+     * Sets new selection path and fires an event
+     */
+    public void setSelectionPath(TreePath path) {
+        try {
+            fireDisabled = true;
+            
+            for (int i = 0; i < path.getPathCount(); i++) {
+                selectRow(path.getPathComponent(i), i, path);
+            }
+        }
+        finally {
+            fireDisabled = false;
+        }
+    }
+    
+    /**
+     * Selects one path component
+     */
+    protected void selectRow(Object row, int index, TreePath path) {
+        if (index > 0 && columns.get(index - 1).getSelectedValue() != row) {
+            columns.get(index - 1).setSelectedValue(row, true);
+        }
+        else { //update
+            updateFromModel(row, index - 1);
+        }
+    }
 
     /**
      * Returns the minumum number of displayed columns.
@@ -198,11 +216,6 @@
             if (columns != null && columns.size() > 0) {
                 for (JList column : columns)
                     column.setCellRenderer(renderer);
-//                Iterator it = columns.iterator();
-//                while (it.hasNext()) {
-//                    JList column = (JList) it.next();
-//                    column.setCellRenderer(renderer);
-//                }
             }
         }
     }
@@ -273,8 +286,7 @@
 
     private BrowserPanel appendColumn() {
         BrowserPanel panel = new BrowserPanel();
-        panel.addListSelectionListener(browserSelector);
-        panel.setCellRenderer(renderer);
+        installColumn(panel);
 
         columns.add(panel);
         JScrollPane scroller =
@@ -292,6 +304,14 @@
         add(scroller);
         return panel;
     }
+    
+    /**
+     * Installs all needed columns and renderers to a new column
+     */
+    protected void installColumn(BrowserPanel panel) {
+        panel.addListSelectionListener(browserSelector);
+        panel.setCellRenderer(renderer);
+    }
 
     private BrowserPanel removeLastColumn() {
         if (columns.size() == 0) {
@@ -300,13 +320,20 @@
 
         int index = columns.size() - 1;
 
-        BrowserPanel panel = (BrowserPanel) columns.remove(index);
-        panel.removeListSelectionListener(browserSelector);
+        BrowserPanel panel = columns.remove(index);
+        uninstallColumn(panel);
 
         // remove ansestor of the column (JScrollPane)
         remove(index);
         return panel;
     }
+    
+    /**
+     * Removes all local listeners from the column
+     */
+    protected void uninstallColumn(BrowserPanel panel) {
+        panel.removeListSelectionListener(browserSelector);
+    }
 
     /**
      * Refreshes preferred size of the browser to
@@ -346,11 +373,19 @@
             viewport.scrollRectToVisible(rectangle);
         }
     }
+    
+    /**
+     * Rebuilds view for the new object selection.
+     */
+    protected synchronized void updateFromModel(Object selectedNode, int panelIndex) {
+        updateFromModel(selectedNode, panelIndex, true);
+    }
 
     /**
      * Rebuilds view for the new object selection.
+     * @param load Whether children are loaded automatically
      */
-    private synchronized void updateFromModel(Object selectedNode, int panelIndex) {
+    protected synchronized void updateFromModel(Object selectedNode, int panelIndex, boolean load) {
         if(selectionPath == null) {
             selectionPath = new Object[0];
         }
@@ -362,7 +397,7 @@
         for (int i = panelIndex + 1;
             i <= lastIndex && i >= 0 && i < columns.size();
             i++) {
-            BrowserPanel column = (BrowserPanel) columns.get(i);
+            BrowserPanel column = columns.get(i);
             column.getSelectionModel().clearSelection();
             column.setRootNode(null);
         }
@@ -370,18 +405,22 @@
         // build path to selected node
         this.selectionPath = rebuildPath(selectionPath, selectedNode, panelIndex);
 
-        // a selectedNode is contained in "panelIndex" column, 
-        // but its children are in the next column.
-        panelIndex++;
-
+        if (load) {
+            // a selectedNode is contained in "panelIndex" column, 
+            // but its children are in the next column.
+            panelIndex++;
+        }
+        
         // expand/contract columns as needed
         adjustViewColumns(panelIndex + 1 - columns.size());
 
-        // selectedNode becomes the root of columns[panelIndex]
-        if (!model.isLeaf(selectedNode)) {
-            BrowserPanel lastPanel = (BrowserPanel) columns.get(panelIndex);
-            lastPanel.setRootNode(selectedNode);
-            scrollToColumn(panelIndex);
+        if (load) {
+            // selectedNode becomes the root of columns[panelIndex]
+            if (!model.isLeaf(selectedNode)) {
+                BrowserPanel lastPanel = columns.get(panelIndex);
+                lastPanel.setRootNode(selectedNode);
+                scrollToColumn(panelIndex);
+            }
         }
 
         fireTreeSelectionEvent(selectionPath);
@@ -436,7 +475,7 @@
     // ====================================================
     // Represents a single browser column
     // ====================================================
-    final class BrowserPanel extends JList {
+    protected final class BrowserPanel extends JList {
         BrowserPanel() {
             BrowserPanel.this.setModel(new ColumnListModel());
             BrowserPanel.this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -487,6 +526,7 @@
             leafRenderer = CellRenderers.listRenderer();
 
             nonLeafTextRenderer = new DefaultListCellRenderer() {
+                @Override
                 public Border getBorder() {
                     return null;
                 }