]> gerrit.simantics Code Review - simantics/platform.git/commitdiff
Merge "Databoard and SCL enchancements."
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Tue, 11 Oct 2016 09:42:05 +0000 (12:42 +0300)
committerGerrit Code Review <gerrit2@www.simantics.org>
Tue, 11 Oct 2016 09:42:05 +0000 (12:42 +0300)
28 files changed:
bundles/org.simantics.acorn/src/org/simantics/acorn/MainState.java
bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/modifiers/StringPropertyModifierRule.java
bundles/org.simantics.db.indexing/src/org/simantics/db/indexing/IndexedRelationsSearcherBase.java
bundles/org.simantics.db.indexing/src/org/simantics/db/indexing/internal/IndexingJob.java [new file with mode: 0644]
bundles/org.simantics.db.procore/src/fi/vtt/simantics/procore/internal/GraphSession.java
bundles/org.simantics.diagram.ontology/graph.tg
bundles/org.simantics.diagram.ontology/graph/DiagramProfiles.pgraph
bundles/org.simantics.diagram.ontology/src/org/simantics/diagram/stubs/DiagramResource.java
bundles/org.simantics.diagram/src/org/simantics/diagram/profile/Profiles.java
bundles/org.simantics.document.server.io/src/org/simantics/document/server/io/Content.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/handlers/e4/SyncActiveModelTypicals.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/handlers/e4/SyncCurrentTypicalInstanceWithTemplate.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/handlers/e4/SyncCurrentTypicalTemplateToInstances.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/typicals/SyncActiveModelTypicals.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/typicals/SyncCurrentTypicalInstanceWithTemplate.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/typicals/SyncCurrentTypicalTemplateToInstances.java
bundles/org.simantics.modeling/src/org/simantics/modeling/typicals/SyncTypicalTemplatesToInstances.java
bundles/org.simantics.scenegraph/.classpath
bundles/org.simantics.scenegraph/META-INF/MANIFEST.MF
bundles/org.simantics.scenegraph/build.properties
bundles/org.simantics.scenegraph/lib/batik-awt-util-1.8.jar [new file with mode: 0644]
bundles/org.simantics.scenegraph/lib/batik-parser-1.8.jar [new file with mode: 0644]
bundles/org.simantics.scenegraph/lib/batik-util-1.8.jar [new file with mode: 0644]
bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/connection/ActionShapes.java [new file with mode: 0644]
bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/connection/HighlightActionPointsAction.java
bundles/org.simantics/META-INF/MANIFEST.MF
bundles/org.simantics/src/org/simantics/SimanticsPlatform.java
features/org.simantics.platform.feature/feature.xml

index ec8451cca78adfef37ea4377cd1fad26e2eff89f..7d158042173089a2034a3b2e5953fc1423fb6e7c 100644 (file)
@@ -157,15 +157,16 @@ public class MainState implements Serializable {
             
             for (Path p : reverseSortedPaths) {
                 if (!p.equals(latest)) {
-                    // this indicates that there is a possibility that index and vg's are out of sync
-                    // if we are able to find folders with higher number than the current head.state
-                    callback.accept(null);
+                    if (Files.exists(p.resolve(HeadState.HEAD_STATE))) {
+                        // this indicates that there is a possibility that index and vg's are out of sync
+                        // if we are able to find folders with higher number than the current head.state
+                        callback.accept(null);
+                    }
                     uncheckedDeleteAll(p);
                 } else {
                     break;
                 }
             }
-            
         }
     }
 
index 28e712750b6316d852c42f615bcf47f276e8a2a1..530c97ea8d9e76d293cb451a22f309e3aada3f29 100644 (file)
@@ -69,12 +69,6 @@ public class StringPropertyModifierRule implements ModifierRule {
             @Override\r
             public String getValue() {\r
                return sm.getValue();\r
-//                try {\r
-//                    return session.syncRequest(new PossibleAdapter<String>(valueResource, String.class));\r
-//                } catch (DatabaseException e) {\r
-//                    ErrorLogger.defaultLogError(e);\r
-//                    return null;\r
-//                }\r
             }\r
             @Override\r
             public String isValid(String label) {\r
@@ -82,18 +76,14 @@ public class StringPropertyModifierRule implements ModifierRule {
             }\r
             @Override\r
             public void modify(final String label) {\r
-                try {\r
-                    session.syncRequest(new WriteRequest() {\r
-                        @Override\r
-                        public void perform(WriteGraph graph) throws DatabaseException {\r
-                               Layer0Utils.addCommentMetadata(graph, "Modify string");\r
-                               graph.markUndoPoint();\r
-                            sm.modify(graph, label);\r
-                        }\r
-                    });\r
-                } catch (DatabaseException e) {\r
-                    ErrorLogger.defaultLogError(e);\r
-                }\r
+                session.asyncRequest(new WriteRequest() {\r
+                    @Override\r
+                    public void perform(WriteGraph graph) throws DatabaseException {\r
+                        Layer0Utils.addCommentMetadata(graph, "Modify string");\r
+                        graph.markUndoPoint();\r
+                        sm.modify(graph, label);\r
+                    }\r
+                }, e -> { if (e != null) ErrorLogger.defaultLogError(e); });\r
             }\r
         };\r
     }\r
index 4afbb174e0eb565958f51a94c0fdfc836d2219bb..c52588af0b37b35f4dfe9e92bf2c0b9bedec5bc9 100644 (file)
@@ -60,15 +60,15 @@ import org.simantics.db.ReadGraph;
 import org.simantics.db.RequestProcessor;\r
 import org.simantics.db.Resource;\r
 import org.simantics.db.Session;\r
-import org.simantics.db.common.request.ReadRequest;\r
 import org.simantics.db.common.request.SafeName;\r
 import org.simantics.db.common.utils.Logger;\r
+import org.simantics.db.common.utils.NameUtils;\r
 import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.indexing.internal.IndexingJob;\r
 import org.simantics.db.layer0.adapter.GenericRelation;\r
 import org.simantics.db.request.Read;\r
 import org.simantics.db.service.CollectionSupport;\r
 import org.simantics.db.service.SerialisationSupport;\r
-import org.simantics.utils.DataContainer;\r
 import org.simantics.utils.FileUtils;\r
 import org.simantics.utils.datastructures.Pair;\r
 \r
@@ -287,19 +287,8 @@ abstract public class IndexedRelationsSearcherBase {
             Field[] fs = makeFieldsForRelation(r, boundLength, document);\r
 \r
             for (Object[] documentData : documentsData) {\r
-                for (int i = 0; i < documentData.length; i++) {\r
-                    Object value = documentData[i];\r
-                    if (value instanceof String) {\r
-                        fs[i].setStringValue((String) value);\r
-                    } else if (value instanceof Long) {\r
-                        fs[i].setLongValue((Long) value);\r
-                    } else {\r
-                        System.err.println("Can only index Long and String fields, encountered " + value);\r
-                        // FIXME: should throw an exception for illegal input data but this would leave the index in an incoherent state\r
-                        continue;\r
-                    }\r
-//                    System.out.println("index " + fs[i].name() + " = " + result[i]);\r
-                }\r
+                if (setFields(fs, documentData) == null)\r
+                    continue;\r
 \r
                 if (IndexPolicy.TRACE_INDEX_UPDATE)\r
                     System.out.println(getDescriptor() + "Inserting document " + document);\r
@@ -413,18 +402,8 @@ abstract public class IndexedRelationsSearcherBase {
                         continue nextDocument;\r
                     }\r
 \r
-                    for (int i = 0; i < documentData.length; i++) {\r
-                        Object value = documentData[i];\r
-                        if (value instanceof String) {\r
-                            fs[i].setStringValue((String) value);\r
-                        } else if (keyValue instanceof Long) {\r
-                            fs[i].setLongValue((Long)value);\r
-                        } else {\r
-                            // FIXME: should throw an exception for illegal input data but this would leave the index in an incoherent state\r
-                            System.err.println("[" + getClass().getSimpleName() + "] Unrecognized document value '" + value + "' for field '" + fs[i].toString() + "', only " + String.class + " and " + Resource.class + " are supported.");\r
-                            continue nextDocument;\r
-                        }\r
-                    }\r
+                    if (setFields(fs, documentData) == null)\r
+                        continue nextDocument;\r
 \r
                     if (IndexPolicy.TRACE_INDEX_UPDATE)\r
                         System.out.println(getDescriptor() + "Replacing document with key " + removedTerm + " with " + document);\r
@@ -606,7 +585,22 @@ abstract public class IndexedRelationsSearcherBase {
         }\r
     });\r
 \r
-    void initializeIndex(IProgressMonitor monitor, ReadGraph graph, final Object[] bound, boolean overwrite) throws IOException,\r
+    void initializeIndex(IProgressMonitor monitor, ReadGraph graph, Object[] bound, boolean overwrite)\r
+            throws IOException, DatabaseException\r
+    {\r
+        IndexingJob.jobifyIfPossible(\r
+                monitor,\r
+                "Reindexing " + NameUtils.getSafeLabel(graph, input),\r
+                mon -> {\r
+                    try {\r
+                        initializeIndexImpl(mon, graph, bound, overwrite);\r
+                    } catch (IOException e) {\r
+                        throw new DatabaseException(e);\r
+                    }\r
+                });\r
+    }\r
+\r
+    void initializeIndexImpl(IProgressMonitor monitor, ReadGraph graph, final Object[] bound, boolean overwrite) throws IOException,\r
     DatabaseException {\r
 \r
         final SubMonitor mon = SubMonitor.convert(monitor, 100);\r
@@ -624,7 +618,7 @@ abstract public class IndexedRelationsSearcherBase {
         final AtomicReference<IndexWriter> writer = new AtomicReference<IndexWriter>();\r
 \r
         try {\r
-            mon.subTask("Create index at " + indexPath.toString());\r
+            mon.subTask("Start index write");\r
             createDirectory(indexPath);\r
 \r
             directory.set(FSDirectory.open(indexPath));\r
@@ -633,95 +627,67 @@ abstract public class IndexedRelationsSearcherBase {
 \r
             mon.worked(5);\r
 \r
-            final DataContainer<Long> start = new DataContainer<Long>();\r
-\r
-            graph.syncRequest(new ReadRequest() {\r
+            final GenericRelation r = graph.adapt(relation, GenericRelation.class);\r
+            if (r == null)\r
+                throw new DatabaseException("Given resource " + graph.syncRequest(new SafeName(relation))\r
+                + "could not be adapted to GenericRelation.");\r
 \r
-                @Override\r
-                public void run(ReadGraph graph) throws DatabaseException {\r
+            long realizeStart = 0;\r
+            if (IndexPolicy.PERF_INDEX_INIT)\r
+                realizeStart = System.nanoTime();\r
 \r
-                    final GenericRelation r = graph.adapt(relation, GenericRelation.class);\r
-                    if (r == null)\r
-                        throw new DatabaseException("Given resource " + graph.syncRequest(new SafeName(relation))\r
-                                + "could not be adapted to GenericRelation.");\r
-\r
-                    mon.worked(45);\r
-\r
-                    GenericRelation selection = r.select(getPattern(r, bound.length), bound);\r
-                    \r
-                    long perfStart = 0;\r
-                    if (IndexPolicy.PERF_INDEX_INIT)\r
-                        perfStart = System.nanoTime();\r
+            mon.subTask("Calculating indexed content");\r
+            GenericRelation selection = r.select(getPattern(r, bound.length), bound);\r
+            mon.worked(5);\r
+            List<Object[]> results = selection.realize(graph);\r
+            mon.worked(40);\r
 \r
-                    final List<Object[]> results = selection.realize(graph);\r
+            if (IndexPolicy.PERF_INDEX_INIT)\r
+                System.out.println(getDescriptor() + "Realized index with " + results.size() + " entries at " + indexPath + " in " + (1e-9 * (System.nanoTime()-realizeStart)) + " seconds.");\r
+            if (IndexPolicy.TRACE_INDEX_INIT)\r
+                System.out.println(getDescriptor() + "Indexed relation " + r + " produced " + results.size() + " results");\r
 \r
-                    if (IndexPolicy.PERF_INDEX_INIT)\r
-                        System.out.println(getDescriptor() + "Realized index at " + indexPath + " in " + (1e-9 * (System.nanoTime()-perfStart)) + " seconds.");\r
-                    \r
-                    if (IndexPolicy.TRACE_INDEX_INIT)\r
-                        System.out.println(getDescriptor() + "Indexed relation " + r + " produced " + results.size() + " results");\r
+            long start = IndexPolicy.PERF_INDEX_INIT ? System.nanoTime() : 0;\r
 \r
-                    if (IndexPolicy.PERF_INDEX_INIT)\r
-                        start.set(System.nanoTime());\r
+            mon.subTask("Indexing content");\r
+            final Semaphore s = new Semaphore(0);\r
+            mon.setWorkRemaining(results.size());\r
 \r
-                    final Semaphore s = new Semaphore(0);\r
+            for (int i = 0; i < INDEXING_THREAD_COUNT; i++) {\r
+                final int startIndex = i;\r
+                executor.submit(() -> {\r
+                    try {\r
+                        Document document = new Document();\r
+                        Field[] fs = makeFieldsForRelation(r, bound.length, document);\r
+\r
+                        for (int index = startIndex; index < results.size(); index += INDEXING_THREAD_COUNT) {\r
+                            if (setFields(fs, results.get(index)) == null)\r
+                                continue;\r
+                            try {\r
+                                writer.get().addDocument(document);\r
+                            } catch (CorruptIndexException e) {\r
+                                throw new IllegalStateException(e);\r
+                            } catch (IOException e) {\r
+                                throw new IllegalStateException(e);\r
+                            } finally {\r
+                                synchronized (mon) {\r
+                                    mon.worked(1);\r
+                                }\r
+                            }\r
+                        }\r
 \r
-                    for(int i=0;i<INDEXING_THREAD_COUNT;i++) {\r
-                       \r
-                       final int startIndex = i;\r
-                       \r
-                       executor.submit(new Runnable() {\r
-\r
-                                                       @Override\r
-                                                       public void run() {\r
-                                                               \r
-                                                               try {\r
-                                                                       \r
-                                                   final Document document = new Document();\r
-\r
-                                                                       Field[] fs = makeFieldsForRelation(r, bound.length, document);\r
-\r
-                                                                       for (int index = startIndex; index < results.size(); index+=INDEXING_THREAD_COUNT) {\r
-                                                                               Object[] result = results.get(index);\r
-                                                                               for (int i = 0; i < result.length; i++) {\r
-                                                                                       Object value = result[i];\r
-                                                                                       if (value instanceof String) {\r
-                                                                                               if (IndexPolicy.DEBUG_INDEX_INIT)\r
-                                                                                                       System.out.println(getDescriptor() + "index " + fs[i].name() + " = " + value + " : String");\r
-                                                                                               fs[i].setStringValue((String) value);\r
-                                                                                       } else if (value instanceof Long) {\r
-                                                                                               if (IndexPolicy.DEBUG_INDEX_INIT)\r
-                                                                                                       System.out.println(getDescriptor() + "index " + fs[i].name() + " = " + value + " : Long");\r
-                                                                                               fs[i].setLongValue((Long) value);\r
-                                                                                       }\r
-                                                                               }\r
-                                                                               try {\r
-                                                                                       writer.get().addDocument(document);\r
-                                                                               } catch (CorruptIndexException e) {\r
-                                                                                       throw new IllegalStateException(e);\r
-                                                                               } catch (IOException e) {\r
-                                                                                       throw new IllegalStateException(e);\r
-                                                                               } finally {\r
-                                                                                       mon.worked(1);\r
-                                                                               }\r
-                                                                       }\r
-                                                               } catch (DatabaseException e) {\r
-                                                                   Logger.defaultLogError("DatabaseException occured during initializing index", e);\r
-                                                               } catch (Throwable t) {\r
-                                                                   Logger.defaultLogError("Fatal error occured during initializing index", t);\r
-                                                               } finally {\r
-                                                                   s.release();\r
-                                                               }\r
-                                                       }\r
-                       });\r
+                        s.release();\r
+                    } catch (DatabaseException e) {\r
+                        throw new IllegalStateException(e);\r
                     }\r
-                    try {\r
-                                               s.acquire(INDEXING_THREAD_COUNT);\r
-                                       } catch (InterruptedException e) {\r
-                                               e.printStackTrace();\r
-                                       }\r
-                }\r
-            });\r
+                });\r
+            }\r
+\r
+            try {\r
+                s.acquire(INDEXING_THREAD_COUNT);\r
+            } catch (InterruptedException e) {\r
+                Logger.defaultLogError(e);\r
+            }\r
 \r
             // http://www.gossamer-threads.com/lists/lucene/java-dev/47895\r
             // and http://lucene.apache.org/java/docs/index.html#27+November+2011+-+Lucene+Core+3.5.0\r
@@ -729,10 +695,10 @@ abstract public class IndexedRelationsSearcherBase {
             //writer.get().optimize();\r
             //writer.get().commit();\r
 \r
-            if (IndexPolicy.PERF_INDEX_INIT) {\r
-                long end = System.nanoTime();\r
-                System.out.println(getDescriptor() + "Wrote index at " + indexPath + " in " + (1e-9 * (end-start.get())) + " seconds.");\r
-            }\r
+            mon.subTask("Flushing");\r
+\r
+            if (IndexPolicy.PERF_INDEX_INIT)\r
+                System.out.println(getDescriptor() + "Wrote index at " + indexPath + " in " + (1e-9 * (System.nanoTime()-start)) + " seconds.");\r
 \r
         } catch (DatabaseException e) {\r
             \r
@@ -1066,4 +1032,23 @@ abstract public class IndexedRelationsSearcherBase {
 \r
     }\r
 \r
+    private Field[] setFields(Field[] fs, Object[] result) {\r
+        for (int i = 0; i < result.length; i++) {\r
+            Object value = result[i];\r
+            if (value instanceof String) {\r
+                if (IndexPolicy.DEBUG_INDEX_INIT)\r
+                    System.out.println(getDescriptor() + "index " + fs[i].name() + " = " + value + " : String");\r
+                fs[i].setStringValue((String) value);\r
+            } else if (value instanceof Long) {\r
+                if (IndexPolicy.DEBUG_INDEX_INIT)\r
+                    System.out.println(getDescriptor() + "index " + fs[i].name() + " = " + value + " : Long");\r
+                fs[i].setLongValue((Long) value);\r
+            } else {\r
+                Logger.defaultLogError("Can only index Long and String fields, encountered " + value);\r
+                return null;\r
+            }\r
+        }\r
+        return fs;\r
+    }\r
+\r
 }\r
diff --git a/bundles/org.simantics.db.indexing/src/org/simantics/db/indexing/internal/IndexingJob.java b/bundles/org.simantics.db.indexing/src/org/simantics/db/indexing/internal/IndexingJob.java
new file mode 100644 (file)
index 0000000..1d00a26
--- /dev/null
@@ -0,0 +1,92 @@
+/*******************************************************************************\r
+ * Copyright (c) 2016 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     Semantum Oy - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.db.indexing.internal;\r
+\r
+import java.util.concurrent.Semaphore;\r
+\r
+import org.eclipse.core.runtime.IProgressMonitor;\r
+import org.eclipse.core.runtime.IStatus;\r
+import org.eclipse.core.runtime.jobs.Job;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.function.DbConsumer;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ * @since 1.22.2, 1.25.0\r
+ */\r
+public abstract class IndexingJob extends Job {\r
+\r
+    /**\r
+     * NOTE: this is intentionally the same as\r
+     * <code>org.simantics.DatabaseJob.DATABASE_JOB_FAMILY</code> in order for\r
+     * this job to cause the same UI-behavior as DatabaseJob.\r
+     */\r
+    private static final String DATABASE_JOB_FAMILY = "org.simantics.db.inDatabaseJob";\r
+\r
+    public IndexingJob(String name) {\r
+        super(name);\r
+    }\r
+\r
+    @Override\r
+    public boolean belongsTo(Object family) {\r
+        return DATABASE_JOB_FAMILY.equals(family);\r
+    }\r
+\r
+    /**\r
+     * @param monitor\r
+     * @param jobName\r
+     * @param consumer\r
+     * @throws DatabaseException\r
+     */\r
+    public static void jobifyIfPossible(\r
+            IProgressMonitor monitor,\r
+            String jobName,\r
+            DbConsumer<IProgressMonitor> consumer)\r
+                    throws DatabaseException\r
+    {\r
+        // Prevent deadlocks by checking preconditions for using a job.\r
+        // JobManager is suspended e.g. during workbench startup.\r
+        if (Job.getJobManager().isSuspended() || Job.getJobManager().currentJob() != null) {\r
+            consumer.accept(monitor);\r
+            return;\r
+        }\r
+\r
+        Semaphore barrier = new Semaphore(0);\r
+        Throwable[] err = { null };\r
+        IndexingJob job = new IndexingJob(jobName) {\r
+            @Override\r
+            protected IStatus run(IProgressMonitor monitor) {\r
+                try {\r
+                    consumer.accept(monitor);\r
+                } catch (Throwable t) {\r
+                    err[0] = t;\r
+                } finally {\r
+                    monitor.done();\r
+                    barrier.release();\r
+                }\r
+                return org.eclipse.core.runtime.Status.OK_STATUS;\r
+            }\r
+        };\r
+        job.schedule();\r
+        try {\r
+            barrier.acquire();\r
+            if (err[0] != null) {\r
+                if (err[0] instanceof DatabaseException)\r
+                    throw (DatabaseException) err[0];\r
+                throw new DatabaseException(err[0]);\r
+            }\r
+        } catch (InterruptedException e) {\r
+            throw new DatabaseException(e);\r
+        }\r
+    }\r
+\r
+}\r
index 58917ae747c7c1fad57995288e745d6b14b7b83a..4ba3722fcbba05d42be25404317872fe8947deb7 100644 (file)
@@ -518,7 +518,7 @@ public abstract class GraphSession {
             @Override\r
             public int read() throws IOException {\r
 \r
-                if(left <= 0) throw new IllegalStateException();\r
+                if(left <= 0) return -1;\r
 \r
                 if(offset == _s.bytes.length) {\r
                     short slen = (short)Math.min(left, IMAX);\r
index eda90ce84ae96db74f5a8ad3256925185924be75..72053ec5b6317e93ecee85e2c5d2afa528b8f79c 100644 (file)
Binary files a/bundles/org.simantics.diagram.ontology/graph.tg and b/bundles/org.simantics.diagram.ontology/graph.tg differ
index 652ea695bb05a9f9577e284530ad538d7edb90ba..072bf49d8f41aed3a47571a2455720202a41df24 100644 (file)
@@ -79,4 +79,8 @@ DIA.Profile.defaultEnabled
   \r
 DIA.ConfigurableProfile <T L0.Entity\r
 \r
+DIA.HasTemplate <R L0.ConsistsOf\r
+   L0.HasDomain DIA.ProfileEntry\r
+   L0.HasRange DIA.ProfileEntry\r
+\r
 \r
index b2cd20448485763a07737d72276af300fef31a9d..f3ec1598b2cbdbc11ace84075aad7825d3d2cc53 100644 (file)
@@ -176,6 +176,8 @@ public class DiagramResource {
     public final Resource HasSymbolContributionFilter;\r
     public final Resource HasSymbol_Inverse;\r
     public final Resource HasTailConnector;\r
+    public final Resource HasTemplate;\r
+    public final Resource HasTemplate_Inverse;\r
     public final Resource HasText;\r
     public final Resource HasText_Inverse;\r
     public final Resource HasTransform;\r
@@ -582,6 +584,8 @@ public class DiagramResource {
         public static final String HasSymbolContributionFilter = "http://www.simantics.org/Diagram-2.2/HasSymbolContributionFilter";\r
         public static final String HasSymbol_Inverse = "http://www.simantics.org/Diagram-2.2/HasSymbol/Inverse";\r
         public static final String HasTailConnector = "http://www.simantics.org/Diagram-2.2/HasTailConnector";\r
+        public static final String HasTemplate = "http://www.simantics.org/Diagram-2.2/HasTemplate";\r
+        public static final String HasTemplate_Inverse = "http://www.simantics.org/Diagram-2.2/HasTemplate/Inverse";\r
         public static final String HasText = "http://www.simantics.org/Diagram-2.2/HasText";\r
         public static final String HasText_Inverse = "http://www.simantics.org/Diagram-2.2/HasText/Inverse";\r
         public static final String HasTransform = "http://www.simantics.org/Diagram-2.2/HasTransform";\r
@@ -998,6 +1002,8 @@ public class DiagramResource {
         HasSymbolContributionFilter = getResourceOrNull(graph, URIs.HasSymbolContributionFilter);\r
         HasSymbol_Inverse = getResourceOrNull(graph, URIs.HasSymbol_Inverse);\r
         HasTailConnector = getResourceOrNull(graph, URIs.HasTailConnector);\r
+        HasTemplate = getResourceOrNull(graph, URIs.HasTemplate);\r
+        HasTemplate_Inverse = getResourceOrNull(graph, URIs.HasTemplate_Inverse);\r
         HasText = getResourceOrNull(graph, URIs.HasText);\r
         HasText_Inverse = getResourceOrNull(graph, URIs.HasText_Inverse);\r
         HasTransform = getResourceOrNull(graph, URIs.HasTransform);\r
index 9baa067f783294da74d63c8ba02c5e339230e277..0246143d24dccd590583e299980c54fc20979a43 100644 (file)
@@ -15,17 +15,28 @@ import java.util.ArrayList;
 import java.util.HashMap;\r
 import java.util.List;\r
 import java.util.Map;\r
+import java.util.Set;\r
 \r
 import org.simantics.databoard.Bindings;\r
 import org.simantics.db.ReadGraph;\r
 import org.simantics.db.Resource;\r
 import org.simantics.db.WriteGraph;\r
+import org.simantics.db.WriteOnlyGraph;\r
 import org.simantics.db.common.request.WriteRequest;\r
 import org.simantics.db.common.utils.ListUtils;\r
 import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.adapter.impl.DefaultCopyHandler;\r
+import org.simantics.db.layer0.adapter.impl.DefaultPasteImportAdvisor;\r
+import org.simantics.db.layer0.util.ClipboardUtils;\r
+import org.simantics.db.layer0.util.SimanticsClipboard;\r
+import org.simantics.db.layer0.util.SimanticsClipboardImpl;\r
+import org.simantics.db.layer0.util.SimanticsKeys;\r
 import org.simantics.db.layer0.variable.Variable;\r
 import org.simantics.db.service.VirtualGraphSupport;\r
 import org.simantics.diagram.stubs.DiagramResource;\r
+import org.simantics.graph.db.TransferableGraphs;\r
+import org.simantics.graph.representation.Root;\r
+import org.simantics.graph.representation.TransferableGraph1;\r
 import org.simantics.layer0.Layer0;\r
 import org.simantics.modeling.ModelingResources;\r
 import org.simantics.simulation.ontology.SimulationResource;\r
@@ -201,7 +212,6 @@ public class Profiles {
                                        for (Resource r : enabled) {\r
                                                graph.claim(p, SIM.IsActive, r);\r
                                        }\r
-\r
                                }\r
                        });\r
                }\r
@@ -255,6 +265,35 @@ public class Profiles {
                Double priority = graph.getPossibleRelatedValue(entry, DIA.ProfileEntry_HasPriority, Bindings.DOUBLE);\r
                if (priority != null) {\r
                        graph.claimLiteral(instance, DIA.ProfileEntry_HasPriority, priority, Bindings.DOUBLE);\r
+               }\r
+               for (Resource template : graph.getObjects(entry, DIA.HasTemplate)) {\r
+                       SimanticsClipboardImpl builder = new SimanticsClipboardImpl();\r
+                       DefaultCopyHandler handler = new DefaultCopyHandler(template);\r
+                       DefaultPasteImportAdvisor advisor = new DefaultPasteImportAdvisor(instance) {\r
+                               @Override\r
+                               public Resource createRoot(WriteOnlyGraph graph, Root root, Resource resource)\r
+                                               throws DatabaseException {\r
+                                       Layer0 l0 = graph.getService(Layer0.class);\r
+                                       DiagramResource DIA = graph.getService(DiagramResource.class);  \r
+                                       \r
+                                       if(resource == null) resource = graph.newResource();\r
+                                       \r
+                                       graph.claim(library, DIA.HasTemplate, DIA.HasTemplate_Inverse, resource);\r
+                                       \r
+                                       String newName = getName(root);\r
+                                       graph.addLiteral(resource, l0.HasName, l0.NameOf, l0.String, newName, Bindings.STRING);\r
+                                       \r
+                                       addRootInfo(root, newName, resource);\r
+                                       \r
+                                       return resource;\r
+                               }\r
+                       };\r
+                       handler.copyToClipboard(graph, builder);\r
+                       for(Set<SimanticsClipboard.Representation> object : builder.getContents()) {\r
+                   TransferableGraph1 tg = ClipboardUtils.accept(graph, object, SimanticsKeys.KEY_TRANSFERABLE_GRAPH);\r
+                   TransferableGraphs.importGraph1(graph, tg, advisor);\r
+               }\r
+                        \r
                }\r
                return instance;\r
        }\r
index ba933d71c33a0ea0f29179ee8a6c52cb65bb392a..eb957c8f62df21833e4c93dde1cc50568e4e806c 100644 (file)
  *******************************************************************************/\r
 package org.simantics.document.server.io;\r
 \r
+import java.io.InputStream;\r
+\r
 public class Content {\r
-       private byte[] data;\r
+       private InputStream input;\r
        private String mimeType;\r
        private long lastModified;\r
+       private int length;\r
        \r
-       public Content(byte[] data, String mimeType, long lastModified) {\r
-               this.data = data;\r
+       public Content(InputStream input, String mimeType, long lastModified, int length) {\r
+               this.input = input;\r
                this.mimeType = mimeType;\r
                this.lastModified = lastModified;\r
+               this.length = length;\r
        }\r
 \r
-       public byte[] getData() {\r
-               return data;\r
+       public InputStream getInputStream() {\r
+               return input;\r
        }\r
 \r
-       public void setData(byte[] data) {\r
-               this.data = data;\r
+       public void setInputStream(InputStream input) {\r
+               this.input = input;\r
        }\r
 \r
        public String getMimeType() {\r
@@ -46,4 +50,12 @@ public class Content {
        public void setLastModified(long lastModified) {\r
                this.lastModified = lastModified;\r
        }\r
+\r
+       public int getLength() {\r
+               return length;\r
+       }\r
+\r
+       public void setLength(int length) {\r
+               this.length = length;\r
+       }\r
 }\r
index bbe8111e54ceb46168a94a6a5e0a0c3954303f94..882e04f0d20db2dcd5cdbd9eaf1b55d4213d97a9 100644 (file)
@@ -80,6 +80,7 @@ public class SyncActiveModelTypicals {
                 RuleChooserDialog.RuleResult result = RuleChooserDialog.choose(shell, msg.toString(), activeModelTypicalTemplates);\r
                 if(result == null) return;\r
 \r
+                session.markUndoPoint();\r
                 SyncTypicalTemplatesToInstances req = new SyncTypicalTemplatesToInstances(result.selectedRules, activeModelTypicalTemplates).logging(result.logging); \r
                 session.syncRequest(req);\r
                 if (result.logging) {\r
index 86bfdc0bbf97c226e1a6d49c508bf7661658f32c..b0d52e355910c316e77f0792778554caee79077f 100644 (file)
@@ -79,6 +79,7 @@ public class SyncCurrentTypicalInstanceWithTemplate {
             RuleChooserDialog.RuleResult result = RuleChooserDialog.choose(shell, "Synchronizing typical instance with its template.", new Resource[] { input.getResource() });\r
             if(result == null) return;\r
 \r
+            session.markUndoPoint();\r
             SyncTypicalTemplatesToInstances req = SyncTypicalTemplatesToInstances.syncSingleInstance(result.selectedRules, input.getResource()).logging(result.logging); \r
             session.syncRequest(req);\r
             if (result.logging) {\r
index f262cd56e53a3868e3ef4c86683769513dad9100..d3d1de3896c45100c4f22d04f03587931d620afd 100644 (file)
@@ -77,6 +77,7 @@ public class SyncCurrentTypicalTemplateToInstances {
             RuleChooserDialog.RuleResult result = RuleChooserDialog.choose(shell, "Synchronizing typical template to all its instances.", new Resource[] { input.getResource() });\r
             if(result == null) return;\r
 \r
+            session.markUndoPoint();\r
             SyncTypicalTemplatesToInstances req = new SyncTypicalTemplatesToInstances(result.selectedRules, input.getResource()).logging(result.logging); \r
             session.syncRequest(req);\r
             if (result.logging) {\r
index 5ba8b9f2d48466d7f5c28248859359307e8ae1a0..441702dd564ad375c92d5e851f0977d8ad13db8a 100644 (file)
@@ -80,6 +80,7 @@ public class SyncActiveModelTypicals extends AbstractHandler {
                 RuleChooserDialog.RuleResult result = RuleChooserDialog.choose(shell, msg.toString(), activeModelTypicalTemplates);\r
                 if(result == null) return null;\r
 \r
+                session.markUndoPoint();\r
                 SyncTypicalTemplatesToInstances req = new SyncTypicalTemplatesToInstances(result.selectedRules, activeModelTypicalTemplates).logging(result.logging); \r
                 session.syncRequest(req);\r
                 if (result.logging) {\r
index df049a6719009e4998bd6d6a36cb0cd25e79db25..3b1455f37057e1f3098b81174d83f511ef069ace 100644 (file)
@@ -44,6 +44,7 @@ public class SyncCurrentTypicalInstanceWithTemplate extends AbstractHandler {
             RuleChooserDialog.RuleResult result = RuleChooserDialog.choose(shell, "Synchronizing typical instance with its template.", new Resource[] { input.getResource() });\r
             if(result == null) return null;\r
 \r
+            session.markUndoPoint();\r
             SyncTypicalTemplatesToInstances req = SyncTypicalTemplatesToInstances.syncSingleInstance(result.selectedRules, input.getResource()).logging(result.logging); \r
             session.syncRequest(req);\r
             if (result.logging) {\r
index 931e3f7fac5da25d3031ec0ecec2227cb1ce25be..c4454831e94412de3990203ea899f64814bd9258 100644 (file)
@@ -43,6 +43,7 @@ public class SyncCurrentTypicalTemplateToInstances extends AbstractHandler {
             RuleChooserDialog.RuleResult result = RuleChooserDialog.choose(shell, "Synchronizing typical template to all its instances in the currently active model.", new Resource[] { input.getResource() });\r
             if(result == null) return null;\r
 \r
+            session.markUndoPoint();\r
             SyncTypicalTemplatesToInstances req = new SyncTypicalTemplatesToInstances(result.selectedRules, input.getResource()).logging(result.logging); \r
             session.syncRequest(req);\r
             if (result.logging) {\r
index 96a33393aeac5bab1c972d995cd1e0754a33ae71..44b2abcfeae8d3ad1915a2bf16a827fd86313730 100644 (file)
@@ -49,6 +49,7 @@ import org.simantics.diagram.handler.CopyPasteStrategy;
 import org.simantics.diagram.handler.ElementObjectAssortment;\r
 import org.simantics.diagram.handler.PasteOperation;\r
 import org.simantics.diagram.handler.Paster;\r
+import org.simantics.diagram.handler.Paster.RouteLine;\r
 import org.simantics.diagram.stubs.DiagramResource;\r
 import org.simantics.diagram.synchronization.CollectingModificationQueue;\r
 import org.simantics.diagram.synchronization.CopyAdvisor;\r
@@ -206,9 +207,9 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
      */\r
     protected Map<Object, Object>            copyMap;\r
 \r
-    final private Map<Resource, List<String>> messageLogs = new HashMap<Resource, List<String>>();\r
+    final private Map<Resource, List<String>> messageLogs = new HashMap<>();\r
     \r
-    public List<Resource> logs = new ArrayList<Resource>();\r
+    public List<Resource> logs = new ArrayList<>();\r
 \r
        private boolean writeLog;\r
 \r
@@ -295,7 +296,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
        if(indexRoot == null) throw new DatabaseException("FATAL: Diagram is not under any index root.");\r
        List<String> log = messageLogs.get(indexRoot);\r
        if(log == null) {\r
-               log = new ArrayList<String>();\r
+               log = new ArrayList<>();\r
                messageLogs.put(indexRoot, log);\r
        }\r
        return log;\r
@@ -331,7 +332,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
         this.syncCtx.set(ModelingSynchronizationHints.MODELING_RESOURCE, ModelingResources.getInstance(graph));\r
 \r
         this.metadata = new TypicalSynchronizationMetadata();\r
-        this.metadata.synchronizedTypicals = new ArrayList<Resource>();\r
+        this.metadata.synchronizedTypicals = new ArrayList<>();\r
 \r
         this.temporaryDiagram = Diagram.spawnNew(DiagramClass.DEFAULT);\r
         this.temporaryDiagram.setHint(SynchronizationHints.CONTEXT, syncCtx);\r
@@ -365,7 +366,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
                 Collection<Resource> libs = graph.syncRequest(new ObjectsWithType(indexRoot, L0.ConsistsOf, DOC.DocumentLibrary));\r
                 if(libs.isEmpty()) continue;\r
 \r
-                List<NamedResource> nrs = new ArrayList<NamedResource>();\r
+                List<NamedResource> nrs = new ArrayList<>();\r
                 for(Resource lib : libs) nrs.add(new NamedResource(NameUtils.getSafeName(graph, lib), lib));\r
                 Collections.sort(nrs, AlphanumComparator.CASE_INSENSITIVE_COMPARATOR);\r
                 Resource library = nrs.iterator().next().getResource();\r
@@ -416,7 +417,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
         if (instances.isEmpty())\r
             return;\r
 \r
-        Set<Resource> templateElements = new THashSet<Resource>( graph.syncRequest(\r
+        Set<Resource> templateElements = new THashSet<>( graph.syncRequest(\r
                 new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) );\r
 \r
         try {\r
@@ -436,7 +437,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
         if (template == null)\r
             return;\r
 \r
-        Set<Resource> templateElements = new THashSet<Resource>( graph.syncRequest(\r
+        Set<Resource> templateElements = new THashSet<>( graph.syncRequest(\r
                 new ObjectsWithType(template, L0.ConsistsOf, DIA.Element) ) );\r
 \r
         try {\r
@@ -499,7 +500,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
         // therefore clone the query result.\r
         typicalInfoBean = (TypicalInfoBean) typicalInfoBean.clone();\r
         typicalInfoBean.templateElements = currentTemplateElements;\r
-        typicalInfoBean.auxiliary = new HashMap<Object, Object>(1);\r
+        typicalInfoBean.auxiliary = new HashMap<>(1);\r
 \r
         TypicalInfo info = new TypicalInfo();\r
         info.monitor = monitor;\r
@@ -523,12 +524,12 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
         // instance elements that do not have a MOD.HasElementSource\r
         // relation but have a MOD.IsTemplatized tag.\r
         Set<Resource> instanceElementsRemovedFromTemplate = findInstanceElementsRemovedFromTemplate(\r
-                graph, info, new THashSet<Resource>(dSizeAbs));\r
+                graph, info, new THashSet<>(dSizeAbs));\r
 \r
         // Find elements in template that do not yet exist in the instance\r
         Set<Resource> templateElementsAddedToTemplate = findTemplateElementsMissingFromInstance(\r
                 graph, currentTemplateElements, info,\r
-                new THashSet<Resource>(dSizeAbs));\r
+                new THashSet<>(dSizeAbs));\r
 \r
         Set<Resource> changedTemplateElements = changedElementsByDiagram.removeValues(template);\r
 \r
@@ -625,7 +626,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
 \r
         ElementObjectAssortment assortment = new ElementObjectAssortment(graph, elementsAddedToTemplate);\r
         if (copyMap == null)\r
-            copyMap = new THashMap<Object, Object>();\r
+            copyMap = new THashMap<>();\r
         else\r
             copyMap.clear();\r
 \r
@@ -666,7 +667,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
 \r
         ModelingResources MOD = ModelingResources.getInstance(graph);\r
         Resource instanceComposite = graph.getPossibleObject(instance, MOD.DiagramToComposite);\r
-        List<Resource> instanceComponents = new ArrayList<Resource>(elementsAddedToTemplate.size());\r
+        List<Resource> instanceComponents = new ArrayList<>(elementsAddedToTemplate.size());\r
 \r
         // Post-process added elements after typicalInfo has been updated and\r
         // template mapping statements are in place.\r
@@ -872,6 +873,17 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
         return changed;\r
     }\r
 \r
+    private static class Connector {\r
+        public final Resource attachmentRelation;\r
+        public final Resource connector;\r
+        public RouteLine attachedTo;\r
+\r
+        public Connector(Resource attachmentRelation, Resource connector) {\r
+            this.attachmentRelation = attachmentRelation;\r
+            this.connector = connector;\r
+        }\r
+    }\r
+\r
     /**\r
      * Synchronizes two route graph connection topologies if and only if the\r
      * destination connection is not attached to any node elements besides\r
@@ -902,19 +914,28 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
             cu = new ConnectionUtil(graph);\r
 \r
         // 0.1. find mappings between source and target connection connectors\r
-        Collection<Resource> targetConnectors = graph.getObjects(targetConnection, DIA.HasConnector);\r
-        for (Resource targetConnector : targetConnectors) {\r
+        Collection<Statement> toTargetConnectors = graph.getStatements(targetConnection, DIA.HasConnector);\r
+        Map<Resource, Connector> targetConnectors = new THashMap<>(toTargetConnectors.size());\r
+        for (Statement toTargetConnector : toTargetConnectors) {\r
+            Resource targetConnector = toTargetConnector.getObject();\r
+            targetConnectors.put(targetConnector, new Connector(toTargetConnector.getPredicate(), targetConnector));\r
             Statement toNode = cu.getConnectedComponentStatement(targetConnection, targetConnector);\r
             if (toNode == null) {\r
                 // Corrupted target connection!\r
-                ErrorLogger.defaultLogError("Encountered corrupted typical template connection " + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance " + NameUtils.getSafeName(graph, targetConnector, true), new Exception("trace"));\r
+                ErrorLogger.defaultLogError("Encountered corrupted typical template connection "\r
+                        + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance "\r
+                        + NameUtils.getSafeName(graph, targetConnector, true) + " that is not attached to any element.",\r
+                        new Exception("trace"));\r
                 return false;\r
             }\r
-\r
-            // Check that the target connections does not connect to\r
-            // non-templatized elements before syncing.\r
-            if (!graph.hasStatement(toNode.getObject(), MOD.IsTemplatized))\r
+            if (!graph.hasStatement(targetConnector, DIA.AreConnected)) {\r
+                // Corrupted target connection!\r
+                ErrorLogger.defaultLogError("Encountered corrupted typical template connection "\r
+                        + NameUtils.getSafeName(graph, targetConnection, true) + " with a stray DIA.Connector instance "\r
+                        + NameUtils.getSafeName(graph, targetConnector, true) + " that is not connected to any other route node.",\r
+                        new Exception("trace"));\r
                 return false;\r
+            }\r
 \r
             //Resource templateNode = typicalInfo.instanceToTemplate.get(toNode.getObject());\r
             Resource templateNode = graph.getPossibleObject(toNode.getObject(), MOD.HasElementSource);\r
@@ -929,7 +950,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
                             t2s.put(targetConnector, templateConnector);\r
 \r
                             if (DEBUG)\r
-                                System.out.println("Mapping connector "\r
+                                debug(typicalInfo, "Mapping connector "\r
                                         + NameUtils.getSafeName(graph, templateConnector, true)\r
                                         + " to " + NameUtils.getSafeName(graph, targetConnector, true));\r
                         }\r
@@ -941,14 +962,17 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
         // 0.2. find mapping between source and target route lines\r
         Collection<Resource> sourceInteriorRouteNodes = graph.getObjects(sourceConnection, DIA.HasInteriorRouteNode);\r
         Collection<Resource> targetInteriorRouteNodes = graph.getObjects(targetConnection, DIA.HasInteriorRouteNode);\r
-        Map<Resource, Paster.RouteLine> sourceToRouteLine = new THashMap<Resource, Paster.RouteLine>();\r
-        Map<Resource, Paster.RouteLine> targetToRouteLine = new THashMap<Resource, Paster.RouteLine>();\r
+        Map<Resource, Paster.RouteLine> sourceToRouteLine = new THashMap<>();\r
+        Map<Resource, Paster.RouteLine> targetToRouteLine = new THashMap<>();\r
 \r
         for (Resource source : sourceInteriorRouteNodes)\r
             sourceToRouteLine.put(source, Paster.readRouteLine(graph, source));\r
         for (Resource target : targetInteriorRouteNodes)\r
             targetToRouteLine.put(target, Paster.readRouteLine(graph, target));\r
 \r
+        Map<Resource, Paster.RouteLine> originalSourceToRouteLine = new THashMap<>(sourceToRouteLine);\r
+        Map<Resource, Paster.RouteLine> originalTargetToRouteLine = new THashMap<>(targetToRouteLine);\r
+\r
         nextSourceLine:\r
             for (Iterator<Map.Entry<Resource, Paster.RouteLine>> sourceIt = sourceToRouteLine.entrySet().iterator(); !targetToRouteLine.isEmpty() && sourceIt.hasNext();) {\r
                 Map.Entry<Resource, Paster.RouteLine> sourceEntry = sourceIt.next();\r
@@ -962,7 +986,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
                         targetIt.remove();\r
 \r
                         if (DEBUG)\r
-                            System.out.println("Mapping routeline "\r
+                            debug(typicalInfo, "Mapping routeline "\r
                                     + NameUtils.getSafeName(graph, sourceEntry.getKey(), true)\r
                                     + " - " + sourceEntry.getValue()\r
                                     + " to " + NameUtils.getSafeName(graph, targetEntry.getKey(), true)\r
@@ -974,20 +998,40 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
             }\r
 \r
         if (DEBUG) {\r
-            System.out.println("Take 1: Source to target route nodes map : " + s2t);\r
-            System.out.println("Take 1: Target to source route nodes map : " + t2s);\r
+            debug(typicalInfo, "Take 1: Source to target route nodes map : " + s2t);\r
+            debug(typicalInfo, "Take 1: Target to source route nodes map : " + t2s);\r
         }\r
 \r
-        // 1.1 remove excess connectors\r
-        for (Resource targetConnector : targetConnectors) {\r
-            if (!t2s.containsKey(targetConnector)) {\r
-               typicalInfo.messageLog.add("\t\t\tremove excess connector from target connection: " + NameUtils.getSafeName(graph, targetConnector));\r
-                cu.removeConnectionPart(targetConnector);\r
-                changed = true;\r
+        // 1.1. Temporarily disconnect instance-specific connectors from the the connection .\r
+        //      They will be added back to the connection after the templatized parts of the\r
+        //      connection have been synchronized.\r
+\r
+        // Stores diagram connectors that are customizations in the synchronized instance.\r
+        List<Connector> instanceOnlyConnectors = null;\r
+\r
+        for (Connector connector : targetConnectors.values()) {\r
+            if (!t2s.containsKey(connector.connector)) {\r
+                typicalInfo.messageLog.add("\t\tencountered instance-specific diagram connector in target connection: " + NameUtils.getSafeName(graph, connector.connector));\r
+\r
+                // Find the RouteLine this connectors is connected to.\r
+                for (Resource rl : graph.getObjects(connector.connector, DIA.AreConnected)) {\r
+                    connector.attachedTo = originalTargetToRouteLine.get(rl);\r
+                    if (connector.attachedTo != null)\r
+                        break;\r
+                }\r
+\r
+                // Disconnect connector from connection\r
+                graph.deny(targetConnection, connector.attachmentRelation, connector.connector);\r
+                graph.deny(connector.connector, DIA.AreConnected);\r
+\r
+                // Keep track of the disconnected connector\r
+                if (instanceOnlyConnectors == null)\r
+                    instanceOnlyConnectors = new ArrayList<>(targetConnectors.size());\r
+                instanceOnlyConnectors.add(connector);\r
             }\r
         }\r
 \r
-        // 1.2 add missing connectors to target\r
+        // 1.2. add missing connectors to target\r
         Collection<Resource> sourceConnectors = graph.getObjects(sourceConnection, DIA.HasConnector);\r
         for (Resource sourceConnector : sourceConnectors) {\r
             if (!s2t.containsKey(sourceConnector)) {\r
@@ -1017,7 +1061,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
 \r
         // 2. sync route lines and their connectivity:\r
         // 2.1. assign correspondences in target for each source route line\r
-        //      by reusing excess routelines in target and by creating new\r
+        //      by reusing excess route lines in target and by creating new\r
         //      route lines.\r
 \r
         Resource[] targetRouteLines = targetToRouteLine.keySet().toArray(Resource.NONE);\r
@@ -1028,7 +1072,7 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
             Resource source = sourceEntry.getKey();\r
             Paster.RouteLine sourceLine = sourceEntry.getValue();\r
 \r
-            typicalInfo.messageLog.add("\t\t\tassign an instance-side routeline complement for " + NameUtils.getSafeName(graph, source, true) + " - " + sourceLine);\r
+            typicalInfo.messageLog.add("\t\t\tassign an instance-side routeline counterpart for " + NameUtils.getSafeName(graph, source, true) + " - " + sourceLine);\r
 \r
             // Assign target route line for source\r
             Resource target = null;\r
@@ -1052,16 +1096,16 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
         }\r
 \r
         if (targetRouteLine >= 0) {\r
-               typicalInfo.messageLog.add("\t\t\tremove excess route lines (" + (targetRouteLine + 1) + ") from target connection");\r
+            typicalInfo.messageLog.add("\t\t\tremove excess route lines (" + (targetRouteLine + 1) + ") from target connection");\r
             for (; targetRouteLine >= 0; targetRouteLine--) {\r
-               typicalInfo.messageLog.add("\t\t\t\tremove excess route line: " + NameUtils.getSafeName(graph, targetRouteLines[targetRouteLine], true));\r
+                typicalInfo.messageLog.add("\t\t\t\tremove excess route line: " + NameUtils.getSafeName(graph, targetRouteLines[targetRouteLine], true));\r
                 cu.removeConnectionPart(targetRouteLines[targetRouteLine]);\r
             }\r
         }\r
 \r
         if (DEBUG) {\r
-            System.out.println("Take 2: Source to target route nodes map : " + s2t);\r
-            System.out.println("Take 2: Target to source route nodes map : " + t2s);\r
+            debug(typicalInfo, "Take 2: Source to target route nodes map : " + s2t);\r
+            debug(typicalInfo, "Take 2: Target to source route nodes map : " + t2s);\r
         }\r
 \r
         // 2.2. Synchronize target connection topology (DIA.AreConnected)\r
@@ -1072,9 +1116,100 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
         changed |= cu.removeExtraInteriorRouteNodes(targetConnection) > 0;\r
         changed |= cu.removeUnusedConnectors(targetConnection) > 0;\r
 \r
+        // 3.1. Ensure that all mapped route nodes in the target connection\r
+        //      are tagged with MOD.IsTemplatized. Future synchronization\r
+        //      can then take advantage of this information to more easily\r
+        //      decide which parts of the connection are originated from\r
+        //      the template and which are not.\r
+        changed |= markMappedRouteNodesTemplatized(graph, s2t.values());\r
+\r
+        // 4. Add temporarily disconnected instance-specific connectors\r
+        //    back to the synchronized connection. The route line to attach\r
+        //    to is based on a simple heuristic.\r
+        if (instanceOnlyConnectors != null) {\r
+            if (originalSourceToRouteLine.isEmpty()) {\r
+                // If there are 0 route lines in the template connection,\r
+                // then one must be added to the instance connection.\r
+                // This can only happen if the template connection is\r
+                // simple, i.e. just between two terminals without any\r
+                // custom routing.\r
+\r
+                // Attach all target connection connectors to the newly created route line\r
+                Resource rl = cu.newRouteLine(targetConnection, null, null);\r
+                for (Resource sourceConnector : sourceConnectors) {\r
+                    Resource targetConnector = s2t.get(sourceConnector);\r
+                    graph.deny(targetConnector, DIA.AreConnected);\r
+                    graph.claim(targetConnector, DIA.AreConnected, DIA.AreConnected, rl);\r
+                }\r
+\r
+                // Copy orientation and position for new route line from original target route lines.\r
+                // This is a simplification that will attach any amount of route lines in the original\r
+                // target connection into just one route line. There is room for improvement here\r
+                // but it will require a more elaborate algorithm to find and cut the non-templatized\r
+                // route lines as well as connectors out of the connection before synchronizing it.\r
+                //\r
+                // TODO: This implementation chooses the added route line position at random if\r
+                //       there are multiple route lines in the target connection.\r
+                if (!originalTargetToRouteLine.isEmpty()) {\r
+                    RouteLine originalRl = originalTargetToRouteLine.values().iterator().next();\r
+                    setRouteLine(graph, rl, originalRl);\r
+                }\r
+\r
+                // Attach the instance specific connectors also to the only route line\r
+                for (Connector connector : instanceOnlyConnectors) {\r
+                    graph.claim(targetConnection, connector.attachmentRelation, connector.connector);\r
+                    graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, rl);\r
+                }\r
+\r
+                changed = true;\r
+            } else {\r
+                for (Connector connector : instanceOnlyConnectors) {\r
+                    // Find the route line that most closely matches the original\r
+                    // route line that the connector was connected to.\r
+                    Resource closestMatch = null;\r
+                    double closestDistance = Double.MAX_VALUE;\r
+                    if (connector.attachedTo != null) {\r
+                        for (Map.Entry<Resource, Paster.RouteLine> sourceLine : originalSourceToRouteLine.entrySet()) {\r
+                            double dist = distance(sourceLine.getValue(), connector.attachedTo);\r
+                            if (dist < closestDistance) {\r
+                                closestMatch = s2t.get(sourceLine.getKey());\r
+                                closestDistance = dist;\r
+                            }\r
+                        }\r
+                    } else {\r
+                        closestMatch = originalSourceToRouteLine.keySet().iterator().next();\r
+                    }\r
+                    graph.claim(targetConnection, connector.attachmentRelation, connector.connector);\r
+                    graph.claim(connector.connector, DIA.AreConnected, DIA.AreConnected, closestMatch);\r
+                    if (closestDistance > 0)\r
+                        changed = true;\r
+                    typicalInfo.messageLog.add("\t\t\treattached instance-specific connector "\r
+                            + NameUtils.getSafeName(graph, connector.connector) + " to nearest existing route line "\r
+                            + NameUtils.getSafeName(graph, closestMatch) + " with distance " + closestDistance);\r
+                }\r
+            }\r
+        }\r
+\r
         return changed;\r
     }\r
 \r
+    private boolean markMappedRouteNodesTemplatized(WriteGraph graph, Iterable<Resource> routeNodes) throws DatabaseException {\r
+        boolean changed = false;\r
+        for (Resource rn : routeNodes) {\r
+            if (!graph.hasStatement(rn, MOD.IsTemplatized)) {\r
+                graph.claim(rn, MOD.IsTemplatized, MOD.IsTemplatized, rn);\r
+                changed = true;\r
+            }\r
+        }\r
+        return changed;\r
+    }\r
+\r
+    private static double distance(RouteLine l1, RouteLine l2) {\r
+        double dist = Math.abs(l2.getPosition() - l1.getPosition());\r
+        dist *= l2.isHorizontal() == l1.isHorizontal() ? 1 : 1000;\r
+        return dist;\r
+    }\r
+\r
     private boolean connectRouteNodes(WriteGraph graph, TypicalInfo typicalInfo, Collection<Resource> sourceRouteNodes) throws DatabaseException {\r
         boolean changed = false;\r
         for (Resource src : sourceRouteNodes) {\r
@@ -1115,6 +1250,15 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
         return changed;\r
     }\r
 \r
+    private void setRouteLine(WriteGraph graph, Resource line, double position, boolean horizontal) throws DatabaseException {\r
+        graph.claimLiteral(line, DIA.HasPosition, L0.Double, position, Bindings.DOUBLE);\r
+        graph.claimLiteral(line, DIA.IsHorizontal, L0.Boolean, horizontal, Bindings.BOOLEAN);\r
+    }\r
+\r
+    private void setRouteLine(WriteGraph graph, Resource line, RouteLine rl) throws DatabaseException {\r
+        setRouteLine(graph, line, rl.getPosition(), rl.isHorizontal());\r
+    }\r
+\r
     private void copyRouteLine(WriteGraph graph, Resource src, Resource tgt) throws DatabaseException {\r
         Double pos = graph.getPossibleRelatedValue(src, DIA.HasPosition, Bindings.DOUBLE);\r
         Boolean hor = graph.getPossibleRelatedValue(src, DIA.IsHorizontal, Bindings.BOOLEAN);\r
@@ -1143,9 +1287,16 @@ public class SyncTypicalTemplatesToInstances extends WriteRequest {
 \r
     private static <K, V> Map<K, V> newOrClear(Map<K, V> current) {\r
         if (current == null)\r
-            return new THashMap<K, V>();\r
+            return new THashMap<>();\r
         current.clear();\r
         return current;\r
     }\r
 \r
+    private void debug(TypicalInfo typicalInfo, String message) {\r
+        if (DEBUG) {\r
+            System.out.println(message);\r
+            typicalInfo.messageLog.add(message);\r
+        }\r
+    }\r
+\r
 }
\ No newline at end of file
index de670af552057cf38a39ea5ad591b3e4b1de721e..47085403ce2aec944cdbe71845e95048990df701 100644 (file)
@@ -1,5 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>\r
 <classpath>\r
+       <classpathentry exported="true" kind="lib" path="lib/batik-awt-util-1.8.jar"/>\r
+       <classpathentry exported="true" kind="lib" path="lib/batik-util-1.8.jar"/>\r
+       <classpathentry exported="true" kind="lib" path="lib/batik-parser-1.8.jar"/>\r
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>\r
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>\r
        <classpathentry kind="src" path="src"/>\r
index 80ef76d7c33ea538ef119bd990ee6cce454537fa..dad71c00d9e81a7828071e1a15633fe11f1c0f11 100644 (file)
@@ -16,7 +16,10 @@ Require-Bundle: gnu.trove3;bundle-version="3.0.0",
  org.simantics.utils;bundle-version="1.1.0"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Bundle-ClassPath: .,
- lib/svgSalamander-tiny.jar
+ lib/svgSalamander-tiny.jar,
+ lib/batik-parser-1.8.jar,
+ lib/batik-awt-util-1.8.jar,
+ lib/batik-util-1.8.jar
 Export-Package: com.kitfox.svg,
  com.kitfox.svg.xml,
  org.simantics.scenegraph,
index 03f349f6cf2e179a37a2a574fb21aaac3a628932..3dca05bdf7393e4501afe86534e43b59dc88c1d2 100644 (file)
@@ -17,4 +17,8 @@ bin.includes = META-INF/,\
                TROVE-README-license.txt,\\r
                TROVE-LICENSE.txt,\\r
                README.txt,\\r
-               JSI-LICENSE.txt
\ No newline at end of file
+               JSI-LICENSE.txt,\\r
+               lib/batik-parser-1.8.jar,\\r
+               lib/batik-awt-util-1.8.jar,\\r
+               lib/batik-util-1.8.jar\r
+\r
diff --git a/bundles/org.simantics.scenegraph/lib/batik-awt-util-1.8.jar b/bundles/org.simantics.scenegraph/lib/batik-awt-util-1.8.jar
new file mode 100644 (file)
index 0000000..ce0be3c
Binary files /dev/null and b/bundles/org.simantics.scenegraph/lib/batik-awt-util-1.8.jar differ
diff --git a/bundles/org.simantics.scenegraph/lib/batik-parser-1.8.jar b/bundles/org.simantics.scenegraph/lib/batik-parser-1.8.jar
new file mode 100644 (file)
index 0000000..01108ad
Binary files /dev/null and b/bundles/org.simantics.scenegraph/lib/batik-parser-1.8.jar differ
diff --git a/bundles/org.simantics.scenegraph/lib/batik-util-1.8.jar b/bundles/org.simantics.scenegraph/lib/batik-util-1.8.jar
new file mode 100644 (file)
index 0000000..cb1c63f
Binary files /dev/null and b/bundles/org.simantics.scenegraph/lib/batik-util-1.8.jar differ
diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/connection/ActionShapes.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/connection/ActionShapes.java
new file mode 100644 (file)
index 0000000..cc44d37
--- /dev/null
@@ -0,0 +1,65 @@
+/*******************************************************************************\r
+ * Copyright (c) 2016 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     Semantum Oy - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.scenegraph.g2d.nodes.connection;\r
+\r
+import java.awt.Shape;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.PathIterator;\r
+import java.awt.geom.Rectangle2D;\r
+import java.io.IOException;\r
+import java.io.StringReader;\r
+\r
+import org.apache.batik.parser.AWTPathProducer;\r
+import org.apache.batik.parser.ParseException;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ * @since 1.22.2, 1.25.0\r
+ */\r
+class ActionShapes {\r
+\r
+       private static final String SCISSOR_PATH = "M 1.0204323,-0.337727 C 0.92746851,-0.449273 0.76034565,-0.444851 0.6356318,-0.396443 l -0.78340687,0.296247 c -0.22844697,-0.124111 -0.45371804,-0.08771 -0.45389716,-0.148477 -1.3989e-4,-0.0475 0.0435015,-0.03722 0.0366086,-0.160853 -0.006619,-0.118717 -0.1308295,-0.206188 -0.24796642,-0.198065 -0.11720479,-3.56e-4 -0.23843025,0.08983 -0.23910595,0.213676 -0.00836,0.124786 0.096897,0.240343 0.221027,0.248145 0.14544563,0.02121 0.40264226,-0.06761 0.5240569,0.148471 -0.0894972,0.166266 -0.24914468,0.167198 -0.39351418,0.159315 -0.11985922,-0.0065 -0.26369532,0.02823 -0.32050912,0.146186 -0.0549,0.113051 -2.567e-4,0.27357 0.12665686,0.307747 0.12812523,0.04659 0.30371326,-0.01328 0.33371488,-0.160538 0.0231253,-0.113504 -0.0573489,-0.166568 -0.0266378,-0.207838 0.0231733,-0.03113 0.17097889,-0.01358 0.4338543,-0.13258 l 0.85013888,0.296862 c 0.10739722,0.02964 0.23851917,0.02826 0.33326488,-0.07755 L 0.14842838,0.004094 1.0204312,-0.337731 Z m -1.7489069,-0.176336 c 0.12383244,0.06866 0.11428878,0.255942 -0.0140755,0.292584 -0.11605716,0.0408 -0.2648432,-0.0717 -0.2281757,-0.197724 0.021388,-0.103377 0.15747907,-0.141864 0.24225133,-0.09486 z m 0.007633,0.765633 c 0.12914301,0.04727 0.1078809,0.265594 -0.0232155,0.295316 -0.0869168,0.03046 -0.2114303,-0.01258 -0.2205326,-0.115113 -0.017329,-0.124578 0.12880443,-0.237615 0.24374818,-0.180208 z";\r
+\r
+       private static final String CROSS_PATH = "M 0.82205219,-1.16919 0.00195748,-0.3491 -0.81813723,-1.16919 -1.1707871,-0.81654 -0.35069244,0.00355 -1.1707871,0.82364 -0.81813723,1.17629 0.00195748,0.3562 0.82205219,1.17629 1.1747021,0.82364 0.35460739,0.00355 l 0,0 0.82009471,-0.82009 z";\r
+\r
+       static Shape parsePath(String path, AffineTransform xform) {\r
+               try {\r
+                       Shape s = AWTPathProducer.createShape(new StringReader(path), PathIterator.WIND_EVEN_ODD);\r
+                       return xform != null ? xform.createTransformedShape(s) : s;\r
+               } catch (ParseException | IOException e) {\r
+                       // Should not happen\r
+                       throw new Error(e);\r
+               }\r
+       }\r
+\r
+       static Shape transformShape(Shape shape, double scaleX, double scaleY, double offsetX, double offsetY, double rotate) {\r
+               AffineTransform tr = new AffineTransform();\r
+               tr.translate(offsetX, offsetY);\r
+               tr.scale(scaleX, scaleY);\r
+               if (rotate != 0)\r
+                       tr.rotate(rotate);\r
+               return tr.createTransformedShape(shape);\r
+       }\r
+\r
+       public static final Shape SCISSOR_SHAPE = parsePath(SCISSOR_PATH, null);\r
+       public static final Shape CROSS_SHAPE = parsePath(CROSS_PATH, null);\r
+\r
+       private static final Rectangle2D SCISSOR_BOUNDS = SCISSOR_SHAPE.getBounds2D();\r
+       private static final Rectangle2D CROSS_BOUNDS = CROSS_SHAPE.getBounds2D();\r
+\r
+       public static final double SCISSOR_WIDTH = SCISSOR_BOUNDS.getWidth();\r
+       public static final double SCISSOR_HEIGHT = SCISSOR_BOUNDS.getHeight();\r
+\r
+       public static final double CROSS_WIDTH = CROSS_BOUNDS.getWidth();\r
+       public static final double CROSS_HEIGHT = CROSS_BOUNDS.getHeight();\r
+\r
+}\r
index 04d08e3ec155242b1b6d58fad89f62adbab6e3d7..7f7bd3cd9b99f434f16c68b6a55adfcd2ab3b357 100644 (file)
@@ -13,23 +13,23 @@ package org.simantics.scenegraph.g2d.nodes.connection;
 \r
 import java.awt.AlphaComposite;\r
 import java.awt.BasicStroke;\r
+import java.awt.Color;\r
 import java.awt.Composite;\r
 import java.awt.Graphics2D;\r
+import java.awt.Shape;\r
 import java.awt.Stroke;\r
 import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Point2D;\r
 import java.awt.geom.Rectangle2D;\r
-import java.awt.image.BufferedImage;\r
-import java.io.IOException;\r
-import java.net.URL;\r
 import java.util.ArrayList;\r
 import java.util.Collection;\r
 \r
-import javax.imageio.ImageIO;\r
-\r
 import org.simantics.diagram.connection.RouteGraph;\r
 import org.simantics.diagram.connection.RouteLineHalf;\r
 import org.simantics.diagram.connection.actions.IAction;\r
 import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;\r
+import org.simantics.scenegraph.utils.Quality;\r
+import org.simantics.scenegraph.utils.QualityHints;\r
 \r
 /**\r
  * @author Tuukka Lehtonen\r
@@ -67,26 +67,25 @@ public class HighlightActionPointsAction implements IAction {
         }\r
     }\r
 \r
-    static BufferedImage                cross;\r
-    static BufferedImage                cut;\r
+    private static final Shape          CROSS_SHAPE             = ActionShapes.CROSS_SHAPE;\r
+    private static final Shape          SCISSOR_SHAPE           = ActionShapes.transformShape(ActionShapes.SCISSOR_SHAPE, 1, 1, 0, 0, -Math.PI/2);\r
 \r
-    static {\r
-        cross = safeReadImage("cross.png");\r
-        cut = safeReadImage("cut.png");\r
-    }\r
+    private static final Color          CROSS_COLOR             = new Color(0xe4, 0x40, 0x61);\r
+    private static final Color          SCISSOR_COLOR           = new Color(20, 20, 20);\r
 \r
     public static final Stroke          STROKE                  = new BasicStroke(0.1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);\r
-    public static final AlphaComposite  COMPOSITE               = AlphaComposite.SrcOver.derive(0.6f);\r
+    public static final AlphaComposite  NO_HIT_COMPOSITE        = AlphaComposite.SrcOver.derive(0.8f);\r
+    public static final AlphaComposite  HIT_COMPOSITE           = AlphaComposite.SrcOver.derive(0.2f);\r
 \r
     public static final double          DEGENERATED_LINE_LENGTH = 1;\r
-    public static final double          CUT_DIST_FROM_END       = 0.5;\r
+    public static final double          CUT_DIST_FROM_END       = 0.75;\r
 \r
     RouteGraph                          rg;\r
 \r
     transient Collection<RouteLineHalf> lhs                     = new ArrayList<RouteLineHalf>();\r
     transient AffineTransform           transform               = new AffineTransform();\r
-    transient AffineTransform           transform2              = new AffineTransform();\r
     transient Rectangle2D               rect                    = new Rectangle2D.Double();\r
+    transient Point2D                   point                   = new Point2D.Double();\r
 \r
     public HighlightActionPointsAction(RouteGraph rg) {\r
         this.rg = rg;\r
@@ -98,94 +97,73 @@ public class HighlightActionPointsAction implements IAction {
 \r
     @Override\r
     public void render(Graphics2D g, IRouteGraphRenderer renderer, double mouseX, double mouseY) {\r
-        // Cannot perform cut or delete segment actions on connections between 2\r
-        // terminals.\r
+        // Cannot perform cut or delete segment actions\r
+        // on connections between 2 terminals.\r
         boolean simpleConnection = (rg.isSimpleConnection() || rg.getTerminals().size() <= 2);\r
         boolean branchedConnection = rg.getTerminals().size() > 2;\r
+        if (!branchedConnection || simpleConnection)\r
+            return;\r
+\r
+        AffineTransform preTr = g.getTransform();\r
+        double realViewScale = 1.0 / getScale(preTr);\r
+        //System.out.println(realViewScale);\r
+        // Don't render any of the actions if they could not be seen anyway.\r
+        if (realViewScale > 0.7)\r
+            return;\r
 \r
         lhs.clear();\r
         rg.getLineHalves(lhs);\r
 \r
-        AffineTransform preTr = g.getTransform();\r
-        double viewScale = 1.0 / getScale(preTr);\r
-        transform2.setToScale(viewScale, viewScale);\r
+        Pick pick = pickAction(rg, lhs, preTr, mouseX, mouseY);\r
+\r
         Composite originalComposite = g.getComposite();\r
-        g.setComposite(COMPOSITE);\r
+        Composite basicComposite = pick.action != null ? HIT_COMPOSITE : NO_HIT_COMPOSITE;\r
+        g.setComposite(basicComposite);\r
 \r
-        Pick pick = pickAction(rg, lhs, preTr, mouseX, mouseY);\r
+        // Always render these in high quality because otherwise the shapes\r
+        // will render with ugly artifacts when zoom level is a bit higher.\r
+        QualityHints origQualityHints = QualityHints.getQuality(g);\r
+        QualityHints.getHints(Quality.HIGH).setQuality(g);\r
 \r
+        // Render line removal markers\r
         if (!simpleConnection) {\r
-            double crossW = cross.getWidth()*viewScale*.5;\r
-            double crossH = cross.getHeight()*viewScale*.5;\r
-\r
-            // Render line removal markers\r
+            g.setPaint(CROSS_COLOR);\r
             for (RouteLineHalf lh : lhs) {\r
-//                if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH)\r
-//                    continue;\r
-//                if (!lh.getLine().isTransient())\r
-//                    continue;\r
-                if (lh.getLine().getTerminal() == null)\r
+                if (removeLocation(lh, point) == null)\r
                     continue;\r
-                double x = lh.getLink().getX();\r
-                double y = lh.getLink().getY();\r
-                if (lh.getLine().isHorizontal()) {\r
-                    x = (lh.getLine().getBegin().getX() + lh.getLine().getEnd().getX()) * .5;\r
-                } else {\r
-                    y = (lh.getLine().getBegin().getY() + lh.getLine().getEnd().getY()) * .5;\r
-                }\r
-\r
                 boolean hit = pick.matches(Action.REMOVE, lh);\r
-\r
                 if (hit)\r
                     g.setComposite(originalComposite);\r
-                transform.setToTranslation(x-crossW, y-crossH);\r
-                g.transform(transform);\r
-                g.drawImage(cross, transform2, null);\r
+                g.translate(point.getX(), point.getY());\r
+                g.fill(CROSS_SHAPE);\r
                 g.setTransform(preTr);\r
                 if (hit)\r
-                    g.setComposite(COMPOSITE);\r
+                    g.setComposite(basicComposite);\r
             }\r
         }\r
 \r
         // Render reconnection markers if the connection is branched.\r
         if (branchedConnection) {\r
-            double cutW = cut.getWidth()*viewScale*.5;\r
-            double cutH = cut.getHeight()*viewScale*.5;\r
-\r
-            final double dist = CUT_DIST_FROM_END;\r
+            g.setPaint(SCISSOR_COLOR);\r
             for (RouteLineHalf lh : lhs) {\r
-                if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH*3)\r
+                if (reconnectLocation(lh, point) == null)\r
                     continue;\r
-                double x = lh.getLink().getX();\r
-                double y = lh.getLink().getY();\r
-                if (lh.getLine().isHorizontal()) {\r
-                    if (lh.getLink() == lh.getLine().getBegin())\r
-                        x += dist*2;\r
-                    else\r
-                        x -= dist*2;\r
-                } else {\r
-                    if (lh.getLink() == lh.getLine().getBegin())\r
-                        y += dist*2;\r
-                    else\r
-                        y -= dist*2;\r
-                }\r
-\r
                 boolean hit = pick.matches(Action.RECONNECT, lh);\r
-\r
                 if (hit)\r
                     g.setComposite(originalComposite);\r
-                transform.setToTranslation(x-cutW, y-cutH);\r
-                if (!lh.getLine().isHorizontal()) {\r
-                    transform.rotate(Math.PI/2, cutW, cutH);\r
-                }\r
+                transform.setToTranslation(point.getX(), point.getY());\r
+                if (!lh.getLine().isHorizontal())\r
+                    transform.rotate(Math.PI/2);\r
+                transform.translate(0, 0.35);\r
                 g.transform(transform);\r
-                g.drawImage(cut, transform2, null);\r
+                g.fill(SCISSOR_SHAPE);\r
                 g.setTransform(preTr);\r
                 if (hit)\r
-                    g.setComposite(COMPOSITE);\r
+                    g.setComposite(basicComposite);\r
             }\r
         }\r
 \r
+        origQualityHints.setQuality(g);\r
         g.setComposite(originalComposite);\r
     }\r
 \r
@@ -205,35 +183,23 @@ public class HighlightActionPointsAction implements IAction {
         if (!branchedConnection || simpleConnection || viewTr == null)\r
             return Pick.MISS;\r
 \r
-        lhs.clear();\r
-        rg.getLineHalves(lhs);\r
+        double viewScale = 1.0 / getScale(viewTr);\r
+        if (viewScale > 0.7)\r
+            return Pick.MISS;\r
 \r
+        double nearest = Double.MAX_VALUE;\r
         RouteLineHalf selected = null;\r
         Action selectedAction = null;\r
-        double nearest = Double.MAX_VALUE;\r
-        double viewScale = 1.0 / getScale(viewTr);\r
 \r
+        // Pick line removal markers\r
         if (!simpleConnection) {\r
-            double crossW = cross.getWidth()*viewScale*.5;\r
-            double crossH = cross.getHeight()*viewScale*.5;\r
-\r
-            // Render line removal markers\r
+            double s = ActionShapes.CROSS_WIDTH * 0.25;\r
             for (RouteLineHalf lh : lhs) {\r
-//                if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH)\r
-//                    continue;\r
-//                if (!lh.getLine().isTransient())\r
-//                    continue;\r
-                if (lh.getLine().getTerminal() == null)\r
+                if (removeLocation(lh, point) == null)\r
                     continue;\r
-                double x = lh.getLink().getX();\r
-                double y = lh.getLink().getY();\r
-                if (lh.getLine().isHorizontal()) {\r
-                    x = (lh.getLine().getBegin().getX() + lh.getLine().getEnd().getX()) * .5;\r
-                } else {\r
-                    y = (lh.getLine().getBegin().getY() + lh.getLine().getEnd().getY()) * .5;\r
-                }\r
-\r
-                rect.setFrameFromCenter(x, y, x-crossW, y-crossH);\r
+                double x = point.getX();\r
+                double y = point.getY();\r
+                rect.setFrameFromCenter(x, y, x-s, y-s);\r
                 boolean hit = rect.contains(mouseX, mouseY);\r
                 if (hit) {\r
                     double distSq = distSq(x, y, mouseX, mouseY);\r
@@ -246,35 +212,21 @@ public class HighlightActionPointsAction implements IAction {
             }\r
         }\r
 \r
-        // Render reconnection markers if the connection is branched.\r
+        // Pick reconnection markers if the connection is branched.\r
         if (branchedConnection) {\r
-            double cutW = cut.getWidth()*viewScale*.5;\r
-            double cutH = cut.getHeight()*viewScale*.5;\r
-\r
-            final double dist = CUT_DIST_FROM_END;\r
+            double w = ActionShapes.SCISSOR_HEIGHT * 0.4;\r
+            double h = ActionShapes.SCISSOR_WIDTH * 0.3;\r
             for (RouteLineHalf lh : lhs) {\r
-                if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH*3)\r
+                if (reconnectLocation(lh, point) == null)\r
                     continue;\r
-                double x = lh.getLink().getX();\r
-                double y = lh.getLink().getY();\r
-                if (lh.getLine().isHorizontal()) {\r
-                    if (lh.getLink() == lh.getLine().getBegin())\r
-                        x += dist*2;\r
-                    else\r
-                        x -= dist*2;\r
-                } else {\r
-                    if (lh.getLink() == lh.getLine().getBegin())\r
-                        y += dist*2;\r
-                    else\r
-                        y -= dist*2;\r
-                }\r
-\r
-                rect.setFrameFromCenter(x, y, x-cutW, y-cutH);\r
+                double x = point.getX();\r
+                double y = point.getY();\r
+                rect.setFrameFromCenter(x, y, x-w, y-h);\r
                 boolean hit = rect.contains(mouseX, mouseY);\r
                 if (hit) {\r
                     double distSq = distSq(x, y, mouseX, mouseY);\r
                     if (distSq < nearest) {\r
-                        nearest = dist;\r
+                        nearest = distSq;\r
                         selected = lh;\r
                         selectedAction = Action.RECONNECT;\r
                     }\r
@@ -285,6 +237,43 @@ public class HighlightActionPointsAction implements IAction {
         return selected == null ? Pick.MISS : new Pick(selectedAction, selected);\r
     }\r
 \r
+    private static Point2D removeLocation(RouteLineHalf lh, Point2D p) {\r
+        if (lh.getLine().getTerminal() == null)\r
+            return null;\r
+\r
+        double x = lh.getLink().getX();\r
+        double y = lh.getLink().getY();\r
+        if (lh.getLine().isHorizontal()) {\r
+            x = (lh.getLine().getBegin().getX() + lh.getLine().getEnd().getX()) * .5;\r
+        } else {\r
+            y = (lh.getLine().getBegin().getY() + lh.getLine().getEnd().getY()) * .5;\r
+        }\r
+        p.setLocation(x, y);\r
+        return p;\r
+    }\r
+\r
+    private static Point2D reconnectLocation(RouteLineHalf lh, Point2D p) {\r
+        if (lh.getLine().getLength() < DEGENERATED_LINE_LENGTH*3)\r
+            return null;\r
+\r
+        final double dist = CUT_DIST_FROM_END;\r
+        double x = lh.getLink().getX();\r
+        double y = lh.getLink().getY();\r
+        if (lh.getLine().isHorizontal()) {\r
+            if (lh.getLink() == lh.getLine().getBegin())\r
+                x += dist*2;\r
+            else\r
+                x -= dist*2;\r
+        } else {\r
+            if (lh.getLink() == lh.getLine().getBegin())\r
+                y += dist*2;\r
+            else\r
+                y -= dist*2;\r
+        }\r
+        p.setLocation(x, y);\r
+        return p;\r
+    }\r
+\r
     private static double distSq(double x1, double y1, double x2, double y2) {\r
         double dx = x2 - x1;\r
         double dy = y2 - y1;\r
@@ -301,13 +290,4 @@ public class HighlightActionPointsAction implements IAction {
         return Math.sqrt(Math.abs(m00*m11 - m10*m01));\r
     }\r
 \r
-    private static BufferedImage safeReadImage(String name) {\r
-        try {\r
-            URL url = HighlightActionPointsAction.class.getResource(name);\r
-            return ImageIO.read(url);\r
-        } catch (IOException e) {\r
-            return null;\r
-        }\r
-    }\r
-\r
 }\r
index 90161d598ece6068fd690d1e0700be38036a9025..48ce23f0e981b5f8b6cd1a989a2868d0943228bc 100644 (file)
@@ -20,7 +20,8 @@ Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
  org.simantics.scl.compiler;bundle-version="0.4.0",
  org.simantics.platform.ui.ontology;bundle-version="1.0.0",
  org.simantics.db.procore;bundle-version="1.1.0",
- org.slf4j.api
+ org.slf4j.api,
+ org.ini4j;bundle-version="0.5.4"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Bundle-ActivationPolicy: lazy
 Export-Package: org.simantics,
index 11013eaf7e32ffd1f09fbc4477ed074c2dad21e7..cf4928fe793e1088d2c28da8587b9ad538d3f370 100644 (file)
@@ -19,6 +19,8 @@ import static org.simantics.db.common.utils.Transaction.writeGraph;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -39,6 +41,8 @@ import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.SubMonitor;
 import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.ini4j.Ini;
+import org.ini4j.InvalidFileFormatException;
 import org.simantics.databoard.Bindings;
 import org.simantics.databoard.Databoard;
 import org.simantics.datatypes.literal.Font;
@@ -155,6 +159,9 @@ public class SimanticsPlatform implements LifecycleListener {
     /** Set to true when the Simantics Platform is in good-and-go condition */
     public boolean running;
 
+    /** ID of the database driver that the platform is currently using */
+    private String currentDatabaseDriver;
+
     /** Database Session */
     public Session session;
     private Management databasebManagement;
@@ -204,24 +211,31 @@ public class SimanticsPlatform implements LifecycleListener {
     private Session setupDatabase(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, PlatformUserAgent userAgent) throws PlatformException {
         if (progressMonitor == null)
             progressMonitor = new NullProgressMonitor();
-        File dbLocation = Platform.getLocation().append("db").toFile();
+        Path workspaceLocation = Platform.getLocation().toFile().toPath();
+        Path dbLocation = workspaceLocation.resolve("db");
+        Path dbIniPath = workspaceLocation.resolve("db.ini");
+        // The driver file overrides any command line arguments to prevent
+        // using the wrong driver for an existing database directory.
         ServerManager serverManager;
         try {
-            serverManager = ServerManagerFactory.create(databaseDriverId, dbLocation.getAbsolutePath());
+            Ini dbIni = loadOrCreateDatabaseIni(dbIniPath, databaseDriverId);
+            databaseDriverId = dbIni.get("driver", "id");
+            serverManager = ServerManagerFactory.create(databaseDriverId, dbLocation.toAbsolutePath().toString());
         } catch (DatabaseException | IOException e) {
-            throw new PlatformException("Failed to initialize Server Manager", e);
+            throw new PlatformException("Failed to initialize database ServerManager with driver " + databaseDriverId, e);
         }
         progressMonitor.beginTask("Setting up Simantics Database", 100);
         progressMonitor.setTaskName("Asserting Database is installed.");
         String msg = "Failed to initialize Simantics database.";
         try {
             // Create database
-            log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Creating database at " + dbLocation));
+            log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Initializing database at " + dbLocation + " with driver " + databaseDriverId));
             progressMonitor.setTaskName("Creating database at " + dbLocation);
-            databasebManagement = serverManager.getManagement(dbLocation);
+            databasebManagement = serverManager.getManagement(dbLocation.toFile());
             databasebManagement.create();
+            currentDatabaseDriver = databaseDriverId;
             // Create layer0.
-            return serverManager.createDatabase(dbLocation);
+            return serverManager.createDatabase(dbLocation.toFile());
         } catch (DatabaseException e) {
             throw new PlatformException(msg, e);
         } catch (Throwable e) {
@@ -970,6 +984,7 @@ public class SimanticsPlatform implements LifecycleListener {
 
             session = null;
             projectResource = null;
+            currentDatabaseDriver = null;
 
             DependenciesRelation.assertFinishedTracking();
 
@@ -1032,6 +1047,8 @@ public class SimanticsPlatform implements LifecycleListener {
 
     public void reconnect(String databaseDriverId) throws Exception {
         // Starts database server.
+        if (currentDatabaseDriver != null)
+            databaseDriverId = currentDatabaseDriver;
         SimanticsPlatform.INSTANCE.startUp(databaseDriverId, null, RecoveryPolicy.ThrowError, OntologyRecoveryPolicy.ThrowError, true, null);
     }
 
@@ -1043,5 +1060,17 @@ public class SimanticsPlatform implements LifecycleListener {
         }
     }
 
-}
+    private Ini loadOrCreateDatabaseIni(Path path, String databaseDriverId)
+            throws InvalidFileFormatException, IOException
+    {
+        File f = path.toFile();
+        Ini dbIni = Files.isRegularFile(path) ? new Ini(f) : new Ini();
+        String iniId = dbIni != null ? dbIni.get("driver", "id") : null;
+        if (iniId == null) {
+            dbIni.put("driver", "id", databaseDriverId);
+            dbIni.store(f);
+        }
+        return dbIni;
+    }
 
+}
index 4dfdc00ff27ad48b9a93b7481d0c8f1ec4b9f88a..1b348f3b2e8a6dca15cf5c71c82d93fb4ae779ba 100644 (file)
          version="0.0.0"\r
          unpack="false"/>\r
 \r
+   <plugin\r
+         id="org.ini4j"\r
+         download-size="0"\r
+         install-size="0"\r
+         version="0.0.0"\r
+         unpack="false"/>\r
+\r
 </feature>\r