You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by mg...@apache.org on 2017/01/05 22:09:49 UTC

[41/50] cayenne-modeler git commit: Added initial dirty handling support via bound properties.

Added initial dirty handling support via bound properties.


Project: http://git-wip-us.apache.org/repos/asf/cayenne-modeler/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne-modeler/commit/de0e60ec
Tree: http://git-wip-us.apache.org/repos/asf/cayenne-modeler/tree/de0e60ec
Diff: http://git-wip-us.apache.org/repos/asf/cayenne-modeler/diff/de0e60ec

Branch: refs/heads/master
Commit: de0e60ecc6020c425a9a539b55ae4135274aa6bc
Parents: aa18f2b
Author: mrg <bl...@gmail.com>
Authored: Wed Oct 12 12:53:36 2016 -0400
Committer: mrg <bl...@gmail.com>
Committed: Wed Oct 12 12:53:36 2016 -0400

----------------------------------------------------------------------
 .../adapters/CayennePropertyAdapter.java        | 30 +++++++++-
 .../modeler/adapters/DataDomainAdapter.java     | 11 ++++
 .../modeler/adapters/DataMapAdapter.java        | 23 ++++++++
 .../modeler/adapters/DataNodeAdapter.java       | 19 +++++++
 .../modeler/adapters/DatabaseEntityAdapter.java | 19 +++++++
 .../adapters/ObjectAttributeAdapter.java        | 20 +++++++
 .../modeler/adapters/ObjectEntityAdapter.java   | 21 +++++++
 .../modeler/layout/MainWindowLayout.java        | 58 +++++++++++++-------
 .../cayenne/modeler/project/CayenneProject.java |  9 +++
 9 files changed, 188 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne-modeler/blob/de0e60ec/src/main/java/org/apache/cayenne/modeler/adapters/CayennePropertyAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/cayenne/modeler/adapters/CayennePropertyAdapter.java b/src/main/java/org/apache/cayenne/modeler/adapters/CayennePropertyAdapter.java
index 6d57759..95a7776 100644
--- a/src/main/java/org/apache/cayenne/modeler/adapters/CayennePropertyAdapter.java
+++ b/src/main/java/org/apache/cayenne/modeler/adapters/CayennePropertyAdapter.java
@@ -19,6 +19,10 @@
 
 package org.apache.cayenne.modeler.adapters;
 
+import org.apache.cayenne.modeler.project.CayenneProject;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.IntegerProperty;
 import javafx.beans.property.StringProperty;
@@ -28,20 +32,40 @@ import javafx.beans.property.adapter.JavaBeanStringPropertyBuilder;
 
 public abstract class CayennePropertyAdapter
 {
+    private static final Log LOGGER = LogFactory.getLog(CayennePropertyAdapter.class);
+
     public BooleanProperty bindBoolean(String property) throws NoSuchMethodException
     {
-        return JavaBeanBooleanPropertyBuilder.create().bean(getWrappedObject()).name(property).build();
+        BooleanProperty boundProperty = JavaBeanBooleanPropertyBuilder.create().bean(getWrappedObject()).name(property).build();
+
+        boundProperty.addListener((observable, newValue, oldValue) -> markProjectDirty());
+
+        return boundProperty;
     }
 
     public IntegerProperty bindInteger(String property) throws NoSuchMethodException
     {
-        return JavaBeanIntegerPropertyBuilder.create().bean(getWrappedObject()).name(property).build();
+        IntegerProperty boundProperty = JavaBeanIntegerPropertyBuilder.create().bean(getWrappedObject()).name(property).build();
+
+        boundProperty.addListener((observable, newValue, oldValue) -> markProjectDirty());
+
+        return boundProperty;
     }
 
     public StringProperty bindString(String property) throws NoSuchMethodException
     {
-        return JavaBeanStringPropertyBuilder.create().bean(getWrappedObject()).name(property).build();
+        StringProperty boundProperty = JavaBeanStringPropertyBuilder.create().bean(getWrappedObject()).name(property).build();
+
+        boundProperty.addListener((observable, newValue, oldValue) -> markProjectDirty());
+
+        return boundProperty;
+    }
+
+    private void markProjectDirty()
+    {
+        getCayennePropject().setDirty(true);
     }
 
+    public abstract CayenneProject getCayennePropject();
     public abstract Object getWrappedObject();
 }

http://git-wip-us.apache.org/repos/asf/cayenne-modeler/blob/de0e60ec/src/main/java/org/apache/cayenne/modeler/adapters/DataDomainAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/cayenne/modeler/adapters/DataDomainAdapter.java b/src/main/java/org/apache/cayenne/modeler/adapters/DataDomainAdapter.java
index 26e3db0..3927e7d 100644
--- a/src/main/java/org/apache/cayenne/modeler/adapters/DataDomainAdapter.java
+++ b/src/main/java/org/apache/cayenne/modeler/adapters/DataDomainAdapter.java
@@ -53,9 +53,14 @@ public class DataDomainAdapter extends CayennePropertyAdapter // implements Adap
         // Must be assigned before property binding.
         this.cayenneProject = cayenneProject;
 
+        // Create adapters for all DataMaps and DataNodes.
         cayenneProject.getDataMaps().stream().forEach(dataMap -> dataMapAdapters.add(new DataMapAdapter(dataMap)));
         cayenneProject.getDataNodes().stream().forEach(dataNode -> dataNodeAdapters.add(new DataNodeAdapter(dataNode)));
 
+        // Associate all DataMaps and DataNodes with this DataDomainAdapter.
+        dataMapAdapters.stream().forEach(dataMapAdapter -> dataMapAdapter.setDataDomainAdapter(this));
+        dataNodeAdapters.stream().forEach(dataNodeAdapter -> dataNodeAdapter.setDataDomainAdapter(this));
+
         try
         {
             nameProperty                      = bindString(DATA_DOMAIN_NAME);
@@ -101,6 +106,12 @@ public class DataDomainAdapter extends CayennePropertyAdapter // implements Adap
     }
 
     @Override
+    public CayenneProject getCayennePropject()
+    {
+        return cayenneProject;
+    }
+
+    @Override
     public Object getWrappedObject()
     {
         return cayenneProject;

http://git-wip-us.apache.org/repos/asf/cayenne-modeler/blob/de0e60ec/src/main/java/org/apache/cayenne/modeler/adapters/DataMapAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/cayenne/modeler/adapters/DataMapAdapter.java b/src/main/java/org/apache/cayenne/modeler/adapters/DataMapAdapter.java
index f999e04..9ec8c03 100644
--- a/src/main/java/org/apache/cayenne/modeler/adapters/DataMapAdapter.java
+++ b/src/main/java/org/apache/cayenne/modeler/adapters/DataMapAdapter.java
@@ -22,6 +22,7 @@ package org.apache.cayenne.modeler.adapters;
 import java.util.List;
 
 import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.modeler.project.CayenneProject;
 import org.apache.commons.lang3.ObjectUtils;
 
 import javafx.beans.property.BooleanProperty;
@@ -40,6 +41,8 @@ public class DataMapAdapter extends CayennePropertyAdapter // implements Adapter
     private final ObservableList<ObjectEntityAdapter>   objectEntityAdapters   = FXCollections.observableArrayList();
     private final ObservableList<DatabaseEntityAdapter> databaseEntityAdapters = FXCollections.observableArrayList();
 
+    private DataDomainAdapter dataDomainAdapter;
+
     private StringProperty nameProperty;
 
     private StringProperty locationProperty;
@@ -89,6 +92,8 @@ public class DataMapAdapter extends CayennePropertyAdapter // implements Adapter
         // Create ObjectEntityAdapters for all object entities.
         dataMap.getObjEntities().stream().forEach(objEntity -> objectEntityAdapters.add(new ObjectEntityAdapter(objEntity)));
 
+        objectEntityAdapters.stream().forEach(objectEntityAdapter -> objectEntityAdapter.setDataMapAdapter(this));
+
         // Sort the ObjectEntityAdapters (by their name).
         sortObjectEntities();
 
@@ -98,6 +103,18 @@ public class DataMapAdapter extends CayennePropertyAdapter // implements Adapter
 
         // Create DatabaseEntityAdapters for all database entities.
         dataMap.getDbEntities().stream().forEach(dbEntity -> databaseEntityAdapters.add(new DatabaseEntityAdapter(dbEntity)));
+
+        databaseEntityAdapters.stream().forEach(databaseEntityAdapter -> databaseEntityAdapter.setDataMapAdapter(this));
+    }
+
+    public DataDomainAdapter getDataDomainAdapter()
+    {
+        return dataDomainAdapter;
+    }
+
+    public void setDataDomainAdapter(DataDomainAdapter dataDomainAdapter)
+    {
+        this.dataDomainAdapter = dataDomainAdapter;
     }
 
     public StringProperty nameProperty() { return nameProperty; }
@@ -167,4 +184,10 @@ public class DataMapAdapter extends CayennePropertyAdapter // implements Adapter
     {
         return dataMap;
     }
+
+    @Override
+    public CayenneProject getCayennePropject()
+    {
+        return dataDomainAdapter.getCayennePropject();
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne-modeler/blob/de0e60ec/src/main/java/org/apache/cayenne/modeler/adapters/DataNodeAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/cayenne/modeler/adapters/DataNodeAdapter.java b/src/main/java/org/apache/cayenne/modeler/adapters/DataNodeAdapter.java
index bd76063..ef6c80c 100644
--- a/src/main/java/org/apache/cayenne/modeler/adapters/DataNodeAdapter.java
+++ b/src/main/java/org/apache/cayenne/modeler/adapters/DataNodeAdapter.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.modeler.adapters;
 
 import org.apache.cayenne.configuration.DataNodeDescriptor;
+import org.apache.cayenne.modeler.project.CayenneProject;
 
 import javafx.beans.property.StringProperty;
 
@@ -35,6 +36,8 @@ public class DataNodeAdapter extends CayennePropertyAdapter
 
     private final DataNodeDescriptor dataNode;
 
+    private DataDomainAdapter dataDomainAdapter;
+
     public DataNodeAdapter(final DataNodeDescriptor dataNode)
     {
         // Must be assigned before property binding.
@@ -53,11 +56,27 @@ public class DataNodeAdapter extends CayennePropertyAdapter
         }
     }
 
+    public DataDomainAdapter getDataDomainAdapter()
+    {
+        return dataDomainAdapter;
+    }
+
+    public void setDataDomainAdapter(DataDomainAdapter dataDomainAdapter)
+    {
+        this.dataDomainAdapter = dataDomainAdapter;
+    }
+
     public StringProperty nameProperty() { return nameProperty; }
     public String getName() { return nameProperty.get(); }
     public void setName(final String value) { nameProperty.set(value); }
 
     @Override
+    public CayenneProject getCayennePropject()
+    {
+        return dataDomainAdapter.getCayennePropject();
+    }
+
+    @Override
     public Object getWrappedObject()
     {
         return dataNode;

http://git-wip-us.apache.org/repos/asf/cayenne-modeler/blob/de0e60ec/src/main/java/org/apache/cayenne/modeler/adapters/DatabaseEntityAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/cayenne/modeler/adapters/DatabaseEntityAdapter.java b/src/main/java/org/apache/cayenne/modeler/adapters/DatabaseEntityAdapter.java
index 8659bbf..08a2363 100644
--- a/src/main/java/org/apache/cayenne/modeler/adapters/DatabaseEntityAdapter.java
+++ b/src/main/java/org/apache/cayenne/modeler/adapters/DatabaseEntityAdapter.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.modeler.project.CayenneProject;
 
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.IntegerProperty;
@@ -35,6 +36,8 @@ public class DatabaseEntityAdapter extends CayennePropertyAdapter // implements
     private final DbEntity databaseEntity;
 //    private BeanPathAdapter<DataMap> dataMapAdapter;
 
+    private DataMapAdapter dataMapAdapter;
+
     private final List<ObjectEntityAdapter> objectEntityAdapters = new ArrayList<>();
 
     private StringProperty nameProperty;
@@ -69,6 +72,16 @@ public class DatabaseEntityAdapter extends CayennePropertyAdapter // implements
         }
     }
 
+    public DataMapAdapter getDataMapAdapter()
+    {
+        return dataMapAdapter;
+    }
+
+    public void setDataMapAdapter(DataMapAdapter dataMapAdapter)
+    {
+        this.dataMapAdapter = dataMapAdapter;
+    }
+
     public StringProperty nameProperty() { return nameProperty; }
     public String getName() { return nameProperty.get(); }
     public void setName(final String value) { nameProperty.set(value); }
@@ -78,6 +91,12 @@ public class DatabaseEntityAdapter extends CayennePropertyAdapter // implements
     public void setLocationProperty(final String value) { locationProperty.set(value); }
 
     @Override
+    public CayenneProject getCayennePropject()
+    {
+        return dataMapAdapter.getCayennePropject();
+    }
+
+    @Override
     public Object getWrappedObject()
     {
         return databaseEntity;

http://git-wip-us.apache.org/repos/asf/cayenne-modeler/blob/de0e60ec/src/main/java/org/apache/cayenne/modeler/adapters/ObjectAttributeAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/cayenne/modeler/adapters/ObjectAttributeAdapter.java b/src/main/java/org/apache/cayenne/modeler/adapters/ObjectAttributeAdapter.java
index c9ad1f4..924fefd 100644
--- a/src/main/java/org/apache/cayenne/modeler/adapters/ObjectAttributeAdapter.java
+++ b/src/main/java/org/apache/cayenne/modeler/adapters/ObjectAttributeAdapter.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.modeler.adapters;
 
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.modeler.project.CayenneProject;
 
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.StringProperty;
@@ -34,6 +35,8 @@ public class ObjectAttributeAdapter extends CayennePropertyAdapter // implements
 
     private final ObjAttribute objectAttribute;
 
+    private ObjectEntityAdapter objectEntityAdapter;
+
     private StringProperty nameProperty;
     private StringProperty javaTypeProperty;
     private StringProperty databaseAttributePathProperty;
@@ -90,6 +93,16 @@ public class ObjectAttributeAdapter extends CayennePropertyAdapter // implements
 //        objectAttribute.getDbAttribute().getName();
     }
 
+    public ObjectEntityAdapter getObjectEntityAdapter()
+    {
+        return objectEntityAdapter;
+    }
+
+    public void setObjectEntityAdapter(ObjectEntityAdapter objectEntityAdapter)
+    {
+        this.objectEntityAdapter = objectEntityAdapter;
+    }
+
     public StringProperty nameProperty() { return nameProperty; }
     public String getName() { return nameProperty.get(); }
     public void setName(final String value) { nameProperty.set(value); }
@@ -110,6 +123,7 @@ public class ObjectAttributeAdapter extends CayennePropertyAdapter // implements
     {
         return TypesMapping.getSqlNameByType(objectAttribute.getDbAttribute().getType());
     }
+
     /**
      * @return The underlying ObjAttribute fronted by this property adapter.
      */
@@ -119,6 +133,12 @@ public class ObjectAttributeAdapter extends CayennePropertyAdapter // implements
     }
 
     @Override
+    public CayenneProject getCayennePropject()
+    {
+        return objectEntityAdapter.getCayennePropject();
+    }
+
+    @Override
     public Object getWrappedObject()
     {
         return objectAttribute;

http://git-wip-us.apache.org/repos/asf/cayenne-modeler/blob/de0e60ec/src/main/java/org/apache/cayenne/modeler/adapters/ObjectEntityAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/cayenne/modeler/adapters/ObjectEntityAdapter.java b/src/main/java/org/apache/cayenne/modeler/adapters/ObjectEntityAdapter.java
index f0bdfa7..f0ed6d4 100644
--- a/src/main/java/org/apache/cayenne/modeler/adapters/ObjectEntityAdapter.java
+++ b/src/main/java/org/apache/cayenne/modeler/adapters/ObjectEntityAdapter.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.modeler.adapters;
 
 import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.modeler.project.CayenneProject;
 
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.StringProperty;
@@ -33,6 +34,8 @@ public class ObjectEntityAdapter extends CayennePropertyAdapter // implements Ad
 
     private final ObjEntity objectEntity;
 
+    private DataMapAdapter dataMapAdapter;
+
     private final ObservableList<ObjectAttributeAdapter> objectAttributeAdapters = FXCollections.observableArrayList();
 
     private StringProperty nameProperty;
@@ -85,6 +88,18 @@ public class ObjectEntityAdapter extends CayennePropertyAdapter // implements Ad
         }
 
         objectEntity.getAttributes().stream().forEach(objAttribute -> objectAttributeAdapters.add(new ObjectAttributeAdapter(objAttribute)));
+
+        objectAttributeAdapters.stream().forEach(objectAttributeAdapter -> objectAttributeAdapter.setObjectEntityAdapter(this));
+    }
+
+    public DataMapAdapter getDataMapAdapter()
+    {
+        return dataMapAdapter;
+    }
+
+    public void setDataMapAdapter(DataMapAdapter dataMapAdapter)
+    {
+        this.dataMapAdapter = dataMapAdapter;
     }
 
     public StringProperty nameProperty() { return nameProperty; }
@@ -105,4 +120,10 @@ public class ObjectEntityAdapter extends CayennePropertyAdapter // implements Ad
     {
         return objectEntity;
     }
+
+    @Override
+    public CayenneProject getCayennePropject()
+    {
+        return dataMapAdapter.getCayennePropject();
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne-modeler/blob/de0e60ec/src/main/java/org/apache/cayenne/modeler/layout/MainWindowLayout.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/cayenne/modeler/layout/MainWindowLayout.java b/src/main/java/org/apache/cayenne/modeler/layout/MainWindowLayout.java
index 1ba4613..f2d2f74 100644
--- a/src/main/java/org/apache/cayenne/modeler/layout/MainWindowLayout.java
+++ b/src/main/java/org/apache/cayenne/modeler/layout/MainWindowLayout.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.modeler.layout;
 
 import java.io.IOException;
+import java.util.Optional;
 
 import org.apache.cayenne.modeler.CayenneModeler;
 import org.apache.cayenne.modeler.adapters.DataMapAdapter;
@@ -39,8 +40,13 @@ import org.apache.cayenne.modeler.project.ObjectEntityTreeItem;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
 import javafx.fxml.FXML;
 import javafx.scene.Node;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Alert.AlertType;
+import javafx.scene.control.ButtonType;
 import javafx.scene.control.TreeItem;
 import javafx.scene.control.TreeView;
 import javafx.scene.layout.AnchorPane;
@@ -75,7 +81,7 @@ public class MainWindowLayout
 
     private CayenneProject cayenneProject;
 
-    private boolean dirty;
+    private StringProperty titleProperty = new SimpleStringProperty();
 
     public MainWindowLayout() throws IOException
     {
@@ -90,6 +96,26 @@ public class MainWindowLayout
                 // event.consume();  <- Prevents window from closing
                 // http://stackoverflow.com/questions/31540500/alert-box-for-when-user-attempts-to-close-application-using-setoncloserequest-in
                 // http://stackoverflow.com/questions/23160573/javafx-stage-setoncloserequest-without-function
+
+                if (cayenneProject.isDirty())
+                {
+                    Alert alert = new Alert(AlertType.CONFIRMATION);
+                    alert.setTitle("Close Window");
+                    alert.setHeaderText("Unsaved Changes");
+                    alert.setContentText("Are you sure you want to close this window and lose your changes?");
+
+                    Optional<ButtonType> result = alert.showAndWait();
+
+                    if (result.get() == ButtonType.OK)
+                    {
+                        // ... user chose OK
+                    }
+                    else
+                    {
+                        // ... user chose CANCEL or closed the dialog
+                        event.consume();
+                    }
+                }
             });
     }
 
@@ -104,15 +130,15 @@ public class MainWindowLayout
         return cayenneProject;
     }
 
-    public void setTitle()
+    private void setTitle()
     {
-        final String edited = isDirty() ? "[edited] " : "";
+        final String edited = cayenneProject.isDirty() ? "[edited] " : "";
         String title = edited + "Cayenne Modeler";
 
         if (cayenneProject != null)
             title += " - " + cayenneProject.getPath();
 
-        super.setTitle(title);
+        titleProperty.set(title);
     }
 
 //    private DataDomainAdapter dataDomainAdapter;
@@ -138,9 +164,14 @@ public class MainWindowLayout
         this.cayenneProject    = cayenneProject;
 //        this.dataDomainAdapter = new DataDomainAdapter(cayenneProject);
 
-      treeRoot.setExpanded(true);
-      treeView.setRoot(treeRoot);
-      treeView.setShowRoot(false);
+        // Wire up the window's title bar to be aware of changes to the dirty indicator.
+        getStage().titleProperty().bind(titleProperty);
+        cayenneProject.dirtyProperty().addListener((observable, oldValue, newValue) -> setTitle());
+        setTitle();
+
+        treeRoot.setExpanded(true);
+        treeView.setRoot(treeRoot);
+        treeView.setShowRoot(false);
 
         // addDataDomain(CayenneModelManager.getModels().get(0));
         // System.out.println(CayenneModelManager.getModels().size());
@@ -377,22 +408,11 @@ public class MainWindowLayout
         CayenneModeler.openPreferences();
     }
 
-    public boolean isDirty()
-    {
-        return dirty;
-    }
-
-    public void setDirty(final boolean dirty)
-    {
-        this.dirty = dirty;
-        setTitle();
-    }
-
     @Override
     public void handleDataDomainChange(final DataDomainChangeEvent event)
     {
         LOGGER.debug("Handling DataDomain Chain Event (Main Window)");
-        setDirty(true);
+//        setDirty(true);
 
         if (event.getEventType() == Type.NAME)
         {

http://git-wip-us.apache.org/repos/asf/cayenne-modeler/blob/de0e60ec/src/main/java/org/apache/cayenne/modeler/project/CayenneProject.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/cayenne/modeler/project/CayenneProject.java b/src/main/java/org/apache/cayenne/modeler/project/CayenneProject.java
index 881b713..f173f2b 100644
--- a/src/main/java/org/apache/cayenne/modeler/project/CayenneProject.java
+++ b/src/main/java/org/apache/cayenne/modeler/project/CayenneProject.java
@@ -44,6 +44,9 @@ import org.apache.cayenne.util.Util;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+
 public class CayenneProject
 {
     private static final Log LOGGER = LogFactory.getLog(CayenneProject.class);
@@ -51,6 +54,12 @@ public class CayenneProject
     private final String path;
     private final DataDomainAdapter dataDomainAdapter;
 
+    private final BooleanProperty dirtyProperty = new SimpleBooleanProperty(false);
+
+    public BooleanProperty dirtyProperty() { return dirtyProperty; };
+    public boolean isDirty() { return dirtyProperty.get(); }
+    public void setDirty(boolean value) { dirtyProperty.set(value); }
+
     public String getPath()
     {
         return path;